如何在Python中使用线程?

我试图理解Python中的线程。我看了留档和示例,但坦率地说,许多示例过于复杂,我很难理解它们。

您如何清楚地显示为多线程划分的任务?

1114560 次浏览

:对于Python中的实际并行化,您应该使用多重处理模块分叉并行执行的多个进程(由于全局解释器锁,Python线程提供交错,但它们实际上是串行执行的,而不是并行执行的,并且仅在交错I/O操作时有用)。

但是,如果你只是在寻找交错(或者正在进行尽管全局解释器锁定但可以并行化的I/O操作),那么线程模块就是开始的地方。作为一个非常简单的例子,让我们考虑通过并行求和子范围来求和大范围的问题:

import threading
class SummingThread(threading.Thread):def __init__(self,low,high):super(SummingThread, self).__init__()self.low=lowself.high=highself.total=0
def run(self):for i in range(self.low,self.high):self.total+=i

thread1 = SummingThread(0,500000)thread2 = SummingThread(500000,1000000)thread1.start() # This actually causes the thread to runthread2.start()thread1.join()  # This waits until the thread has completedthread2.join()# At this point, both threads have completedresult = thread1.total + thread2.totalprint result

请注意,上面是一个非常愚蠢的例子,因为它绝对没有I/O,并且由于全局解释器锁,它将在CPython中串行执行,尽管是交错的(增加了上下文切换的开销)。

这是一个简单的例子:您需要尝试几个替代URL并返回第一个URL的内容以进行响应。

import Queueimport threadingimport urllib2
# Called by each threaddef get_url(q, url):q.put(urllib2.urlopen(url).read())
theurls = ["http://google.com", "http://yahoo.com"]
q = Queue.Queue()
for u in theurls:t = threading.Thread(target=get_url, args = (q,u))t.daemon = Truet.start()
s = q.get()print s

在这种情况下,线程被用作一个简单的优化:每个子线程都在等待一个URL来解析和响应,以将其内容放入队列中;每个线程都是一个守护进程(如果主线程结束,则不会保持进程运行-这比不更常见);主线程启动所有子线程,在队列中执行get等待,直到其中一个执行了put,然后发出结果并终止(这会删除任何可能仍在运行的子线程,因为它们是守护线程)。

在Python中正确使用线程总是与I/O操作相关(因为CPython无论如何都不使用多个内核来运行CPU绑定的任务,线程的唯一原因是在等待一些I/O时不会阻塞进程)。顺便说一句,队列几乎总是将工作外包给线程和/或收集工作结果的最佳方式,而且它们本质上是线程安全的,所以它们让你不必担心锁、条件、事件、信号量和其他线程间协调/通信概念。

与前面提到的其他人一样,由于GIL,CPython只能将线程用于I/O等待。

如果您想从CPU密集型任务的多个内核中受益,请使用多重处理

from multiprocessing import Process
def f(name):print 'hello', name
if __name__ == '__main__':p = Process(target=f, args=('bob',))p.start()p.join()

对我来说,线程的完美示例是监控异步事件。看看这段代码。

# thread_test.pyimport threadingimport time
class Monitor(threading.Thread):def __init__(self, mon):threading.Thread.__init__(self)self.mon = mon
def run(self):while True:if self.mon[0] == 2:print "Mon = 2"self.mon[0] = 3;

您可以通过打开IPython会话并执行以下操作来播放此代码:

>>> from thread_test import Monitor>>> a = [0]>>> mon = Monitor(a)>>> mon.start()>>> a[0] = 2Mon = 2>>>a[0] = 2Mon = 2

再等几分钟

>>> a[0] = 2Mon = 2

只是注意:线程不需要队列。

这是我能想到的最简单的例子,显示了10个进程同时运行。

import threadingfrom random import randintfrom time import sleep

def print_number(number):
# Sleeps a random 1 to 10 secondsrand_int_var = randint(1, 10)sleep(rand_int_var)print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"
thread_list = []
for i in range(1, 10):
# Instantiates the thread# (i) does not make a sequence, so (i,)t = threading.Thread(target=print_number, args=(i,))# Sticks the thread in a list so that it remains accessiblethread_list.append(t)
# Starts threadsfor thread in thread_list:thread.start()
# This blocks the calling thread until the thread whose join() method is called is terminated.# From http://docs.python.org/2/library/threading.html#thread-objectsfor thread in thread_list:thread.join()
# Demonstrates that the main process waited for threads to completeprint "Done"

来自Alex Martelli的回答帮助了我。然而,这里有一个我认为更有用的修改版本(至少对我来说)。

更新:适用于Python 2和Python 3

try:# For Python 3import queuefrom urllib.request import urlopenexcept:# For Python 2import Queue as queuefrom urllib2 import urlopen
import threading
worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']
# Load up a queue with your data. This will handle lockingq = queue.Queue()for url in worker_data:q.put(url)
# Define a worker functiondef worker(url_queue):queue_full = Truewhile queue_full:try:# Get your data off the queue, and do some workurl = url_queue.get(False)data = urlopen(url).read()print(len(data))
except queue.Empty:queue_full = False
# Create as many threads as you wantthread_count = 5for i in range(thread_count):t = threading.Thread(target=worker, args = (q,))t.start()

我发现这非常有用:创建尽可能多的线程作为核心,让它们执行(大量)任务(在这种情况下,调用shell程序):

import Queueimport threadingimport multiprocessingimport subprocess
q = Queue.Queue()for i in range(30): # Put 30 tasks in the queueq.put(i)
def worker():while True:item = q.get()# Execute a task: call a shell program and wait until it completessubprocess.call("echo " + str(item), shell=True)q.task_done()
cpus = multiprocessing.cpu_count() # Detect number of coresprint("Creating %d threads" % cpus)for i in range(cpus):t = threading.Thread(target=worker)t.daemon = Truet.start()
q.join() # Block until all tasks are done

自从这个问题在2010年被问到以来,如何使用地图的Python进行简单的多线程处理已经得到了真正的简化。

下面的代码来自一篇你绝对应该查看的文章/博客文章(没有隶属关系)-一行并行:日常线程任务的更好模型。我将在下面总结-它最终只是几行代码:

from multiprocessing.dummy import Pool as ThreadPoolpool = ThreadPool(4)results = pool.map(my_function, my_array)

这是多线程版本:

results = []for item in my_array:results.append(my_function(item))

特性介绍

Map是一个很酷的小函数,也是轻松将并行性注入Python代码的关键。对于那些不熟悉的人来说,map是从Lisp等函数式语言中提取的东西。它是一个在序列上映射另一个函数的函数。

Map为我们处理序列的迭代,应用函数,并将所有结果存储在最后的一个方便的列表中。

在此输入图片描述


实施

map函数的并行版本由两个库提供:多处理,以及它鲜为人知但同样神奇的步骤子:multiprocessing.dummy.

multiprocessing.dummy与多重处理模块而是使用线程一个重要的区别-使用多个进程的CPU密集型任务;线程(和期间)I/O)完全相同:

multiprocessing.dummy复制了多重处理的API,但只不过是线程模块的包装器。

import urllib2from multiprocessing.dummy import Pool as ThreadPool
urls = ['http://www.python.org','http://www.python.org/about/','http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html','http://www.python.org/doc/','http://www.python.org/download/','http://www.python.org/getit/','http://www.python.org/community/','https://wiki.python.org/moin/',]
# Make the Pool of workerspool = ThreadPool(4)
# Open the URLs in their own threads# and return the resultsresults = pool.map(urllib2.urlopen, urls)
# Close the pool and wait for the work to finishpool.close()pool.join()

以及计时结果:

Single thread:   14.4 seconds4 Pool:   3.1 seconds8 Pool:   1.4 seconds13 Pool:   1.3 seconds

传递多个参数(像这样工作仅在Python 3.3及更高版本中):

要传递多个数组:

results = pool.starmap(function, zip(list_a, list_b))

或者传递一个常量和一个数组:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

如果您使用的是早期版本的Python,您可以通过这个解决方法)传递多个参数。

(感谢user136036的有用评论。

使用全新的concurrent.futures模块

def sqr(val):import timetime.sleep(0.1)return val * val
def process_result(result):print(result)
def process_these_asap(tasks):import concurrent.futures
with concurrent.futures.ProcessPoolExecutor() as executor:futures = []for task in tasks:futures.append(executor.submit(sqr, task))
for future in concurrent.futures.as_completed(futures):process_result(future.result())# Or instead of all this just do:# results = executor.map(sqr, tasks)# list(map(process_result, results))
def main():tasks = list(range(10))print('Processing {} tasks'.format(len(tasks)))process_these_asap(tasks)print('Done')return 0
if __name__ == '__main__':import syssys.exit(main())

遗嘱执行人的方法似乎熟悉所有那些谁得到他们的手脏与Java。

另一个侧面说明:为了保持宇宙的理智,如果你不使用with上下文,不要忘记关闭你的池/执行器(这太棒了,它可以为你做)

给定一个函数f,像这样线程它:

import threadingthreading.Thread(target=f).start()

将参数传递给f

threading.Thread(target=f, args=(a,b,c)).start()

这是一个简单的多线程示例,它会很有帮助。你可以运行它并轻松理解Python中的多线程是如何工作的。我使用了一个锁来阻止对其他线程的访问,直到前面的线程完成它们的工作。通过使用这行代码,

tLock=线程. BoundedSemaphore(值=4)

您可以一次允许多个进程,并保持其余的线程,这些线程将在稍后或完成之前的进程后运行。

import threadingimport time
#tLock = threading.Lock()tLock = threading.BoundedSemaphore(value=4)def timer(name, delay, repeat):print  "\r\nTimer: ", name, " Started"tLock.acquire()print "\r\n", name, " has the acquired the lock"while repeat > 0:time.sleep(delay)print "\r\n", name, ": ", str(time.ctime(time.time()))repeat -= 1
print "\r\n", name, " is releaseing the lock"tLock.release()print "\r\nTimer: ", name, " Completed"
def Main():t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))
t1.start()t2.start()t3.start()t4.start()t5.start()
print "\r\nMain Complete"
if __name__ == "__main__":Main()

以前的解决方案都没有在我的GNU/Linux服务器上使用多核(我没有管理员权限)。它们只是在单核上运行。

我使用较低级别的os.fork接口生成多个进程。这是为我工作的代码:

from os import fork
values = ['different', 'values', 'for', 'threads']
for i in range(len(values)):p = fork()if p == 0:my_function(values[i])break

Python 3具有启动并行任务的功能。这使我们的工作更容易。

它有线程池进程池

以下给出了一个见解:

线程池实例来源

import concurrent.futuresimport urllib.request
URLS = ['http://www.foxnews.com/','http://www.cnn.com/','http://europe.wsj.com/','http://www.bbc.co.uk/','http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contentsdef load_url(url, timeout):with urllib.request.urlopen(url, timeout=timeout) as conn:return conn.read()
# We can use a with statement to ensure threads are cleaned up promptlywith concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:# Start the load operations and mark each future with its URLfuture_to_url = {executor.submit(load_url, url, 60): url for url in URLS}for future in concurrent.futures.as_completed(future_to_url):url = future_to_url[future]try:data = future.result()except Exception as exc:print('%r generated an exception: %s' % (url, exc))else:print('%r page is %d bytes' % (url, len(data)))

进程池执行人来源

import concurrent.futuresimport math
PRIMES = [112272535095293,112582705942171,112272535095293,115280095190773,115797848077099,1099726899285419]
def is_prime(n):if n % 2 == 0:return False
sqrt_n = int(math.floor(math.sqrt(n)))for i in range(3, sqrt_n + 1, 2):if n % i == 0:return Falsereturn True
def main():with concurrent.futures.ProcessPoolExecutor() as executor:for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':main()

大多数留档和教程都使用Python的ThreadingQueue模块,对于初学者来说,它们似乎让人不知所措。

也许考虑一下Python 3的concurrent.futures.ThreadPoolExecutor模块。

结合with子句和列表理解,它可能是一个真正的魅力。

from concurrent.futures import ThreadPoolExecutor, as_completed
def get_url(url):# Your actual program here. Using threading.Lock() if necessaryreturn ""
# List of URLs to fetchurls = ["url1", "url2"]
with ThreadPoolExecutor(max_workers = 5) as executor:
# Create threadsfutures = {executor.submit(get_url, url) for url in urls}
# as_completed() gives you the threads once finishedfor f in as_completed(futures):# Get the resultsrs = f.result()

我在这里看到了很多没有执行实际工作的示例,而且它们大多是CPU受限的。这是一个CPU受限任务的示例,该任务计算1000万和1005万之间的所有素数。我在这里使用了所有四种方法:

import mathimport timeitimport threadingimport multiprocessingfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def time_stuff(fn):"""Measure time of execution of a function"""def wrapper(*args, **kwargs):t0 = timeit.default_timer()fn(*args, **kwargs)t1 = timeit.default_timer()print("{} seconds".format(t1 - t0))return wrapper
def find_primes_in(nmin, nmax):"""Compute a list of prime numbers between the given minimum and maximum arguments"""primes = []
# Loop from minimum to maximumfor current in range(nmin, nmax + 1):
# Take the square root of the current numbersqrt_n = int(math.sqrt(current))found = False
# Check if the any number from 2 to the square root + 1 divides the current numnber under considerationfor number in range(2, sqrt_n + 1):
# If divisible we have found a factor, hence this is not a prime number, lets move to the next oneif current % number == 0:found = Truebreak
# If not divisible, add this number to the list of primes that we have found so farif not found:primes.append(current)
# I am merely printing the length of the array containing all the primes, but feel free to do what you wantprint(len(primes))
@time_stuffdef sequential_prime_finder(nmin, nmax):"""Use the main process and main thread to compute everything in this case"""find_primes_in(nmin, nmax)
@time_stuffdef threading_prime_finder(nmin, nmax):"""If the minimum is 1000 and the maximum is 2000 and we have four workers,1000 - 1250 to worker 11250 - 1500 to worker 21500 - 1750 to worker 31750 - 2000 to worker 4so let’s split the minimum and maximum values according to the number of workers"""nrange = nmax - nminthreads = []for i in range(8):start = int(nmin + i * nrange/8)end = int(nmin + (i + 1) * nrange/8)
# Start the thread with the minimum and maximum split up to compute# Parallel computation will not work here due to the GIL since this is a CPU-bound taskt = threading.Thread(target = find_primes_in, args = (start, end))threads.append(t)t.start()
# Don’t forget to wait for the threads to finishfor t in threads:t.join()
@time_stuffdef processing_prime_finder(nmin, nmax):"""Split the minimum, maximum interval similar to the threading method above, but use processes this time"""nrange = nmax - nminprocesses = []for i in range(8):start = int(nmin + i * nrange/8)end = int(nmin + (i + 1) * nrange/8)p = multiprocessing.Process(target = find_primes_in, args = (start, end))processes.append(p)p.start()
for p in processes:p.join()
@time_stuffdef thread_executor_prime_finder(nmin, nmax):"""Split the min max interval similar to the threading method, but use a thread pool executor this time.This method is slightly faster than using pure threading as the pools manage threads more efficiently.This method is still slow due to the GIL limitations since we are doing a CPU-bound task."""nrange = nmax - nminwith ThreadPoolExecutor(max_workers = 8) as e:for i in range(8):start = int(nmin + i * nrange/8)end = int(nmin + (i + 1) * nrange/8)e.submit(find_primes_in, start, end)
@time_stuffdef process_executor_prime_finder(nmin, nmax):"""Split the min max interval similar to the threading method, but use the process pool executor.This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.RECOMMENDED METHOD FOR CPU-BOUND TASKS"""nrange = nmax - nminwith ProcessPoolExecutor(max_workers = 8) as e:for i in range(8):start = int(nmin + i * nrange/8)end = int(nmin + (i + 1) * nrange/8)e.submit(find_primes_in, start, end)
def main():nmin = int(1e7)nmax = int(1.05e7)print("Sequential Prime Finder Starting")sequential_prime_finder(nmin, nmax)print("Threading Prime Finder Starting")threading_prime_finder(nmin, nmax)print("Processing Prime Finder Starting")processing_prime_finder(nmin, nmax)print("Thread Executor Prime Finder Starting")thread_executor_prime_finder(nmin, nmax)print("Process Executor Finder Starting")process_executor_prime_finder(nmin, nmax)if __name__ == "__main__":main()

这是我的Mac OS X四核机器上的结果

Sequential Prime Finder Starting9.708213827005238 secondsThreading Prime Finder Starting9.81836523200036 secondsProcessing Prime Finder Starting3.2467174359990167 secondsThread Executor Prime Finder Starting10.228896902000997 secondsProcess Executor Finder Starting2.656402041000547 seconds

这是使用线程的CSV导入的非常简单的示例。(库包含可能因不同目的而异。)

助手功能:

from threading import Threadfrom project import appimport csv

def import_handler(csv_file_name):thr = Thread(target=dump_async_csv_data, args=[csv_file_name])thr.start()
def dump_async_csv_data(csv_file_name):with app.app_context():with open(csv_file_name) as File:reader = csv.DictReader(File)for row in reader:# DB operation/query

驱动功能:

import_handler(csv_file_name)
import threadingimport requests
def send():
r = requests.get('https://www.stackoverlow.com')
thread = []t = threading.Thread(target=send())thread.append(t)t.start()

借用这篇文章,我们知道在多线程、多重处理和async/asyncio及其用法之间进行选择。

python3有一个新的内置库,以便实现并发和并行-concurrent.futures

因此,我将通过一个实验来演示通过Threading-Pool运行四个任务(即.sleep()方法):

from concurrent.futures import ThreadPoolExecutor, as_completedfrom time import sleep, time
def concurrent(max_worker):futures = []tic = time()with ThreadPoolExecutor(max_workers=max_worker) as executor:futures.append(executor.submit(sleep, 2))  # Two seconds sleepfutures.append(executor.submit(sleep, 1))futures.append(executor.submit(sleep, 7))futures.append(executor.submit(sleep, 3))for future in as_completed(futures):if future.result() is not None:print(future.result())print(f'Total elapsed time by {max_worker} workers:', time()-tic)
concurrent(5)concurrent(4)concurrent(3)concurrent(2)concurrent(1)

输出:

Total elapsed time by 5 workers: 7.007831811904907Total elapsed time by 4 workers: 7.007944107055664Total elapsed time by 3 workers: 7.003149509429932Total elapsed time by 2 workers: 8.004627466201782Total elapsed time by 1 workers: 13.013478994369507

[]:

  • 正如您在上面的结果中看到的,最好的情况是这四个任务的<修>3工作者。
  • 如果您有一个进程任务而不是I/O绑定或阻塞(multiprocessing而不是threading),您可以将ThreadPoolExecutor更改为ProcessPoolExecutor

我想用一个简单的例子和我发现有用的解释来帮助我自己解决这个问题。

在这个答案中,你会发现一些关于Python的GIL(全局解释器锁)的信息和一个使用multiprocessing.dummy编写的简单的日常示例以及一些简单的基准测试。

全局解释器锁(GIL)

Python不允许真正意义上的多线程。它有一个多线程包,但如果你想多线程来加快你的代码速度,那么使用它通常不是一个好主意。

Python有一个称为全局解释器锁(GIL)的构造。GIL确保在任何时候只有一个“线程”可以执行。一个线程获取GIL,做一点工作,然后将GIL传递到下一个线程。

这种情况发生得非常快,因此在人眼看来,您的线程似乎是并行执行的,但它们实际上只是轮流使用相同的CPU内核。

所有这些GIL传递都会增加执行开销。这意味着如果你想让你的代码运行得更快,那么使用线程这不是一个好主意

使用Python的线程包是有原因的。如果你想同时运行一些东西,并且效率不是问题,或者如果你正在运行需要等待某些东西(比如一些I/O)的代码,那么它可能会很有意义。但是线程库不会让你使用额外的CPU内核。

多线程可以外包给操作系统(通过执行多处理),以及调用您的Python代码的一些外部应用程序(例如,Sparkhadoop),或者您的Python代码调用的一些代码(例如:您可以让您的Python代码调用一个执行昂贵的多线程内容的C函数)。

为什么这很重要

因为很多人花了很多时间试图在他们花哨的Python多线程代码中找到瓶颈,然后才了解GIL是什么。

一旦这些信息清楚了,这是我的代码:

#!/bin/pythonfrom multiprocessing.dummy import Poolfrom subprocess import PIPE,Popenimport timeimport os
# In the variable pool_size we define the "parallelness".# For CPU-bound tasks, it doesn't make sense to create more Pool processes# than you have cores to run them on.## On the other hand, if you are using I/O-bound tasks, it may make sense# to create a quite a few more Pool processes than cores, since the processes# will probably spend most their time blocked (waiting for I/O to complete).pool_size = 8
def do_ping(ip):if os.name == 'nt':print ("Using Windows Ping to " + ip)proc = Popen(['ping', ip], stdout=PIPE)return proc.communicate()[0]else:print ("Using Linux / Unix Ping to " + ip)proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)return proc.communicate()[0]

os.system('cls' if os.name=='nt' else 'clear')print ("Running using threads\n")start_time = time.time()pool = Pool(pool_size)website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]result = {}for website_name in website_names:result[website_name] = pool.apply_async(do_ping, args=(website_name,))pool.close()pool.join()print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))
# Now we do the same without threading, just to compare timeprint ("\nRunning NOT using threads\n")start_time = time.time()for website_name in website_names:do_ping(website_name)print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))
# Here's one way to print the final output from the threadsoutput = {}for key, value in result.items():output[key] = value.get()print ("\nOutput aggregated in a Dictionary:")print (output)print ("\n")
print ("\nPretty printed output: ")for key, value in output.items():print (key + "\n")print (value)

作为第二个anwser的python3版本:

import queue as Queueimport threadingimport urllib.request
# Called by each threaddef get_url(q, url):q.put(urllib.request.urlopen(url).read())
theurls = ["http://google.com", "http://yahoo.com", "http://www.python.org","https://wiki.python.org/moin/"]
q = Queue.Queue()def thread_func():for u in theurls:t = threading.Thread(target=get_url, args = (q,u))t.daemon = Truet.start()
s = q.get()    
def non_thread_func():for u in theurls:get_url(q,u)        

s = q.get()   

你可以测试一下:

start = time.time()thread_func()end = time.time()print(end - start)
start = time.time()non_thread_func()end = time.time()print(end - start)

non_thread_func()花费的时间是thread_func()的4倍

这很容易理解。这里有两种简单的线程化方法。

import timefrom concurrent.futures import ThreadPoolExecutor, as_completedimport threading
def a(a=1, b=2):print(a)time.sleep(5)print(b)return a+b
def b(**kwargs):if "a" in kwargs:print("am b")else:print("nothing")        
to_do=[]executor = ThreadPoolExecutor(max_workers=4)ex1=executor.submit(a)to_do.append(ex1)ex2=executor.submit(b, **{"a":1})to_do.append(ex2)
for future in as_completed(to_do):print("Future {} and Future Return is {}\n".format(future, future.result()))
print("threading")
to_do=[]to_do.append(threading.Thread(target=a))to_do.append(threading.Thread(target=b, kwargs={"a":1}))
for threads in to_do:threads.start()    
for threads in to_do:threads.join()

下面的代码可以运行10线程并发打印从099的数字:

from threading import Thread
def test():for i in range(0, 100):print(i)
thread_list = []
for _ in range(0, 10):thread = Thread(target=test)thread_list.append(thread)
for thread in thread_list:thread.start()
for thread in thread_list:thread.join()

并且,下面的代码是上面运行10线程并发的代码中的速记#0循环版本,打印从099的数字:

from threading import Thread
def test():[print(i) for i in range(0, 100)]
thread_list = [Thread(target=test) for _ in range(0, 10)]
[thread.start() for thread in thread_list]
[thread.join() for thread in thread_list]

这是下面的结果:

...99839784989985868788...