多线程
资料来源链接: 菜鸟教程.
Python中使用线程有两种方式:函数或者用类来包装线程对象。
函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:
_thread.start_new_thread ( function, args[, kwargs] )
参数说明:
function - 线程函数。
args - 传递给线程函数的参数,他必须是个tuple类型。
kwargs - 可选参数。
示例
import _thread
import time
# 需要线程执行的函数
def print_time(threadName,delay):
print("开启线程,线程id",_thread.get_ident())
count = 0
while count<5:
time.sleep(delay)
count += 1
# ctime 以本地时区时间显示时间
print("%s:%s"%(threadName,time.ctime(time.time())))
try:
# 开始创建一个新的线程
_thread.start_new_thread(print_time,("thread_1",2))
_thread.start_new_thread(print_time,("thread_2",4))
except:
print("无法启动新的线程")
# 保持主线程20秒存活时间
time.sleep(20)
需要注意的是,这种情况子线程会随时主线程的结束而结束,切记保存主线程的存活
运行结果
开启线程,线程id 26376
开启线程,线程id 24868
thread_1:Sat Jan 23 22:23:36 2021
thread_1:Sat Jan 23 22:23:38 2021
thread_2:Sat Jan 23 22:23:38 2021
thread_1:Sat Jan 23 22:23:40 2021
thread_2:Sat Jan 23 22:23:42 2021
thread_1:Sat Jan 23 22:23:42 2021
thread_1:Sat Jan 23 22:23:44 2021
thread_2:Sat Jan 23 22:23:46 2021
thread_2:Sat Jan 23 22:23:50 2021
线程模块
Python3 通过两个标准库 _thread 和 threading 提供对线程的支持。
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
run(): 用以表示线程活动的方法。
start():启动线程活动。
join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
import threading
import time
exitFlag = 0
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
"""集成原始类,构造自己的线程类"""
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print(f"start thread {self.name}")
print_time(self.name,self.counter,5)
print(f"exit thread {self.name}")
def print_time(threadName,delay,counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print(f"{threadName},{time.ctime(time.time())},{counter}")
counter -= 1
# 创建新线程
thread1 = myThread(1,"thread-1",1)
thread2 = myThread(2,"thread-2",2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("exit root Thread")
执行结果
start thread thread-1
start thread thread-2
thread-1,Sat Jan 23 22:50:20 2021,5
thread-2,Sat Jan 23 22:50:21 2021,5
thread-1,Sat Jan 23 22:50:21 2021,4
thread-1,Sat Jan 23 22:50:22 2021,3
thread-2,Sat Jan 23 22:50:23 2021,4
thread-1,Sat Jan 23 22:50:23 2021,2
thread-1,Sat Jan 23 22:50:24 2021,1
exit thread thread-1
thread-2,Sat Jan 23 22:50:25 2021,3
thread-2,Sat Jan 23 22:50:27 2021,2
thread-2,Sat Jan 23 22:50:29 2021,1
exit thread thread-2
exit root Thread
线程同步
同一进程中的线程变量是共享的
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。
锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
import threading
import time
exitFlag = 0
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
"""集成原始类,构造自己的线程类"""
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print(f"start thread {self.name}")
# 获取锁
threadLock.acquire()
print_time(self.name,self.counter,5)
print(f"exit thread {self.name}")
# 释放锁
threadLock.release()
def print_time(threadName,delay,counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print(f"{threadName},{time.ctime(time.time())},{counter}")
counter -= 1
# 线程锁
threadLock = threading.Lock()
# 线程集合
threads = []
# 创建新线程
thread1 = myThread(1,"thread-1",1)
thread2 = myThread(2,"thread-2",2)
thread1.start()
thread2.start()
threads.append(thread1)
threads.append(thread2)
for t in threads:
t.join()
print("exit root Thread")
执行结果
start thread thread-1
start thread thread-2
thread-1,Sat Jan 23 23:00:44 2021,5
thread-1,Sat Jan 23 23:00:45 2021,4
thread-1,Sat Jan 23 23:00:46 2021,3
thread-1,Sat Jan 23 23:00:47 2021,2
thread-1,Sat Jan 23 23:00:48 2021,1
exit thread thread-1
thread-2,Sat Jan 23 23:00:50 2021,5
thread-2,Sat Jan 23 23:00:52 2021,4
thread-2,Sat Jan 23 23:00:54 2021,3
thread-2,Sat Jan 23 23:00:56 2021,2
thread-2,Sat Jan 23 23:00:58 2021,1
exit thread thread-2
exit root Thread
Process finished with exit code 0
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作
import threading
import time
import queue
exitFlag = 0
class myThread(threading.Thread):
def __init__(self,threadID,name,q):
"""集成原始类,构造自己的线程类"""
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q
def run(self):
print(f"start thread {self.name}")
process_data(self.name,self.q)
print(f"exit thread {self.name}")
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = q.get()
queueLock.release()
print ("%s processing %s" % (threadName, data))
else:
queueLock.release()
time.sleep(1)
# 将要创建的线程ID
threadList = ["Thread-1","Thread-2","Thread-3"]
# 将要抢占的数据
nameList = ["One", "Two", "Three", "Four", "Five"]
# 线程锁
queueLock = threading.Lock()
# 创建线程队列
workQueue = queue.Queue(10)
# 线程集合
threads = []
# 开始创建线程
for idx,tName in enumerate(threadList):
thread = myThread(idx+1,tName,workQueue)
thread.start()
threads.append(thread)
# 填充队列
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
# 等待队列清空
while not workQueue.empty():
pass
# 通知主线程退出
exitFlag = 1
# 等待所有线程完成
for t in threads:
t.join()
print("exit root Thread")
执行结果
start thread Thread-1
start thread Thread-2
start thread Thread-3
Thread-3 processing One
Thread-2 processing Two
Thread-1 processing Three
Thread-3 processing Four
Thread-1 processing Five
exit thread Thread-2
exit thread Thread-3
exit thread Thread-1
exit root Thread
线程池
原文链接: link.
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。
此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。
线程池的使用
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。
如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。
Exectuor 提供了如下常用方法:
- submit(fn, *args, **kwargs):将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。
- map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
- shutdown(wait=True):关闭线程池。
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
Future 提供了如下方法:
- cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
- cancelled():返回 Future 代表的线程任务是否被成功取消。
running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。 - done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
- result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。
- exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
- add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
- 在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。
使用线程池来执行线程任务的步骤如下:
- 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
定义一个普通函数作为线程任务。 - 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
- 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
下面程序示范了如何使用线程池来执行线程任务:
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def test(value1,value2=None):
print(f"threading is printed {threading.current_thread().name},{value1},{value2}")
time.sleep(2)
return "finished"
if __name__ == '__main__':
threadPool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_")
for i in range(10):
future = threadPool.submit(test,i)
threadPool.shutdown()
执行结果
threading is printed test__0,0,None
threading is printed test__1,1,None
threading is printed test__2,2,None
threading is printed test__3,3,None
threading is printed test__0,4,None
threading is printed test__1,5,None
threading is printed test__2,6,None
threading is printed test__3,7,None
threading is printed test__0,8,None
threading is printed test__2,9,None
获取执行结果
前面程序调用了 Future 的 result() 方法来获取线程任务的运回值,但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。
如果程序不希望直接调用 result() 方法阻塞线程,则可通过 Future 的 add_done_callback() 方法来添加回调函数,该回调函数形如 fn(future)。当线程任务完成后,程序会自动触发该回调函数,并将对应的 Future 对象作为参数传给该回调函数。
直接调用result函数结果
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def test(value1,value2=None):
print(f"threading is printed {threading.current_thread().name},{value1},{value2}")
time.sleep(2)
return "finished"
if __name__ == '__main__':
threadPool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_")
for i in range(10):
future = threadPool.submit(test,i)
print(future.result())
threadPool.shutdown()
结果
threading is printed test__0,0,None
finished
threading is printed test__0,1,None
finished
threading is printed test__1,2,None
finished
threading is printed test__0,3,None
finished
threading is printed test__2,4,None
finished
threading is printed test__1,5,None
finished
threading is printed test__3,6,None
finished
threading is printed test__0,7,None
finished
threading is printed test__2,8,None
finished
threading is printed test__1,9,None
finished
Process finished with exit code 0
回调函数执行
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def test(value1,value2=None):
print(f"threading is printed {threading.current_thread().name},{value1},{value2}")
time.sleep(2)
return "finished"
def test_result(future):
print(future.result())
if __name__ == '__main__':
threadPool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_")
for i in range(10):
future = threadPool.submit(test,i)
future.add_done_callback(test_result)
threadPool.shutdown()
结果
threading is printed test__0,0,None
threading is printed test__1,1,None
threading is printed test__2,2,None
threading is printed test__3,3,None
finished
finished
threading is printed test__2,4,None
threading is printed test__1,5,None
finished
threading is printed test__0,6,None
finished
threading is printed test__3,7,None
finished
threading is printed test__0,8,None
finished
threading is printed test__1,9,None
finished
finished
finished
finished
Process finished with exit code 0
另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池,如下面的程序所示。
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def test(value1,value2=None):
print(f"threading is printed {threading.current_thread().name},{value1},{value2}")
time.sleep(2)
return "finished"
def test_result(future):
print(future.result())
if __name__ == '__main__':
# threadPool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_")
# for i in range(10):
# future = threadPool.submit(test,i)
# future.add_done_callback(test_result)
# threadPool.shutdown()
# 上下文管理协议
with ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_") as threadPool:
for i in range(10):
future = threadPool.submit(test,i)
future.add_done_callback(test_result)
此外,Exectuor 还提供了一个 map(func, *iterables, timeout=None, chunksize=1) 方法,该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。
例如,如下程序使用 Executor 的 map() 方法来启动线程,并收集线程任务的返回值:
示例换成多参数的:
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def test(value1,value2=None):
print(f"threading is printed {threading.current_thread().name},{value1},{value2}")
time.sleep(2)
return "finished"
def test_result(future):
print(future.result())
if __name__ == '__main__':
# threadPool = ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_")
# for i in range(10):
# future = threadPool.submit(test,i)
# future.add_done_callback(test_result)
# threadPool.shutdown()
# 上下文管理协议
# with ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_") as threadPool:
# for i in range(10):
# future = threadPool.submit(test,i)
# future.add_done_callback(test_result)
# map
threadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")
threadPool.map(test,[i for i in range(10)],[i for i in range(10)])
threadPool.shutdown()
结果
threading is printed test__0,0,0
threading is printed test__1,1,1
threading is printed test__2,2,2
threading is printed test__3,3,3
threading is printed test__0,4,4
threading is printed test__2,5,5
threading is printed test__1,6,6
threading is printed test__3,7,7
threading is printed test__2,8,8
threading is printed test__0,9,9
面程序使用 map() 方法来启动 4个线程(该程序的线程池包含 4 个线程,如果继续使用只包含两个线程的线程池,此时将有一个任务处于等待状态,必须等其中一个任务完成,线程空闲出来才会获得执行的机会),map() 方法的返回值将会收集每个线程任务的返回结果。
通过上面程序可以看出,使用 map() 方法来启动线程,并收集线程的执行结果,不仅具有代码简单的优点,而且虽然程序会以并发方式来执行 test() 函数,但最后收集的 test() 函数的执行结果,依然与传入参数的结果保持一致。
线程定时器
原文链接: link.
一.线程定时器Timer原理
原理比较简单,指定时间间隔后启动线程!适用场景:完成定时任务,例如:定时提醒-闹钟等等.
导入线程模块
import threading
timer = threading.Timer(interval, function, args=None, kwargs=None)
参数介绍:
interval — 定时器间隔,间隔多少秒之后启动定时器任务(单位:秒);
function — 线程函数;
args — 线程参数,可以传递元组类型数据,默认为空(缺省参数);
kwargs — 线程参数,可以传递字典类型数据,默认为空(缺省参数);
单次执行
from threading import Timer
import time
def single():
print(time.ctime(time.time()),"定时函数 被执行")
# 创建定时线程
print(time.ctime(time.time()),"创建定时函数,5秒后执行")
t1 = Timer(interval=5,function=single)
t1.start()
结果
Sun Jan 24 00:11:34 2021
Sun Jan 24 00:11:39 2021 定时函数 被执行
定时循环执行
from threading import Timer
import time
def mulit():
print(time.ctime(time.time()),"定时器执行")
global t2
t2 = Timer(interval=5,function=mulit)
t2.start()
t2 = Timer(interval=5,function=mulit)
t2.start()
# 保持主线程存活
time.sleep(20)
# 取消执行器
t2.cancel()
结果
Sun Jan 24 00:17:00 2021 定时器执行
Sun Jan 24 00:17:05 2021 定时器执行
Sun Jan 24 00:17:10 2021 定时器执行