线程是计算机程序中的最小执行单元。多线程,可以理解为在一个统一的进程内,共享同一块内存空间,主线程调用不同的角色去并发完成任务。
由于python的GTL限制(每个进程同一时间只许有一个线程参与CPU运算),线程大多不需要CPU参与,较适合I\O密集型程序使用。但,多线程在进行上下文交互时较慢。
一、创建简单的多线程
主线程将任务分配给多个子线程后,不用理会子线程的完成情况,主线程继续向下执行;然后等待子线程全部完成后,主线程结束并退出(默认情况)。
# 需求:停顿一秒后,打印输出0-9 import time def func(i): time.sleep(1) print(i) # 一、普通方法实现:先打印数字,再print时间 start_time = time.time() for i in range(10): func(i) run_time = time.time() - start_time print('nomal:',run_time) # nomal: 10.0 # 二、使用多线程:先print时间,再打印数字 import threading start_time = time.time() for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start() run_time = time.time() - start_time print('thread:%f' % run_time) # thread:0.003906
二、线程锁 RLock
线程锁:锁定一块内存空间数据,使其他线程无法共享该数据,直至完成相应操作后线程锁打开。数据被释放后可继续共享使用。
由于同一进程中的多个线程是共享内存的,当需要对同一数据进行修改时,未避免出现脏数据,需使用线程锁进行绑定。
#---------------------线程锁------------------------- # 需求: 1+2+3+。。。+10 = ? import threading import time total_num = 0 # # 不使用线程锁: 由于线程间并发执行,且谁先谁后都是随机的,所以出现脏数据,结果混乱。 # def func(i): # time.sleep(0.5) # global total_num # total_num += i # print(i,total_num,) # 使用线程锁,可得到正确的输出结果 lock = threading.RLock() def func(i): lock.acquire() # 开启线程锁 time.sleep(0.5) global total_num total_num += i lock.release() # 释放线程锁 print(i,total_num,) for i in range(11): t = threading.Thread(target=func,args=(i,)) t.start()
三、线程间通信 Event
使用同一事件标识,同步控制多线程操作。Event默认为False,产生阻塞;可通过set设置为True,取消阻塞、向下执行。
# ------------------------多线程间通信Event----------------------------- import threading import time def func(event,i): print(i,'before...') event.wait() # 阻塞:等待True print(i,'after...') event_obj = threading.Event() # 创建通信对象 # how to use for i in range(10): t = threading.Thread(target=func,args=(event_obj,i,)) t.start() time.sleep(1) # 若通信对象未设置为True,进行设置 if not event_obj.isSet(): event_obj.set() # 清除Event的设置,还原默认值False if event_obj.isSet: event_obj.clear()
四、前台线程和后台线程
需要主线程等待完成的子线程,是前台线程(默认情况);不需要主线程等待完成的子线程,是后台线程。可通过 setDaemon 和 join 进行设置。
(一)setDaemon: True时后天线程,False时前台线程
import threading import time def func(i): time.sleep(1) print('hi,func,',i) for i in range(3): t = threading.Thread(target=func,args=(i,)) t.setDaemon(True) # 设置为后台线程 # t.setDaemon(False) # 设置为前台线程 t.start() print('the end !')
(二)join: 设置主线程等待子线程的最多时间
import threading import time def func(i): time.sleep(i) print('hi,func,',i) for i in range(3): t = threading.Thread(target=func,args=(i,)) t.start() t.join(3) # 最多等待时间 print('the end !')
五、线程池
在实际应用过程中,由于线程共享同一块内存空间,所以线程不是越多越好。一般情况,会在程序中设定一个线程池,指定好可使用线程的数量,通常最大为20.
(一)简单版线程池:创建固定长度的queue,每个queue中元素存放一个线程
此方法创建的线程池,在线程未使用前已经创建,在执行任务过程中,有些线程一直空闲等待中;每一个线程执行完任务后被抛弃,需要重新添加线程到线程池,保证容量稳定。
#----------------------------------线程池------------ import threading import queue import time class ThreadPool(object): def __init__(self,MaxNum = 20): self.q = queue.Queue(MaxNum) for i in range(MaxNum): self.q.put(threading.Thread) def get(self): return self.q.get() def put(self): self.q.put(threading.Thread) def f(pool,args): print(i) pool.put() # 任务执行完成后,原线程无效并等待垃圾回收,需重新添加线程到queue,保证线程池容量稳定 pool = ThreadPool(10) # 创建线程池 for i in range(4): ret = pool.get() # 取线程 t = ret(target=f,args=(pool,i)) t.start()
(二)绝版线程池:创建固定长度的queue放任务,随机创建线程去执行任务,线程执行完任务后重新去领取新任务,直至全部任务完成,线程被抛弃。
#----------------------------绝版线程池------------------------ import queue import threading import contextlib import time StopEvent = object() # 全局变量,任务完结时的标识 class ThreadPool(object): def __init__(self, max_num, max_task_num = None): if max_task_num: # 将任务装进列表,max_task_num表示最多任务量,即列表长度 self.q = queue.Queue(max_task_num) else: self.q = queue.Queue() # 默认放无限多个任务 self.max_num = max_num # 最多创建的线程数(线程池的最大容量) self.cancel = False self.terminal = False # 是否终止正在执行的任务的标识 self.generate_list = [] # 真实创建的线程列表 self.free_list = [] # 空闲的线程列表 def run(self, func, args, callback=None): """ 线程池执行一个任务 :param func: 任务函数 :param args: 任务函数所需参数 :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态; 2、任务函数返回值(默认为None,即:不执行回调函数) :return: 如果线程池已经终止,则返回True否则None """ if self.cancel: return if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: # 如果没有空闲线程,且暂时未达到线程池的最大额度,创建新线程 self.generate_thread() w = (func, args, callback,) # 待执行任务包,含任务信息:函数、参数、返回调用。 self.q.put(w) # 将任务元组添加至queue def generate_thread(self): """ 创建一个线程 """ t = threading.Thread(target=self.call) # 创建新线程,并执行call方法 t.start() def call(self): """ 循环去获取任务函数并执行任务函数 """ # 将当前新创建的线程添加到列表 current_thread = threading.currentThread() self.generate_list.append(current_thread) # 领取任务,并执行 event = self.q.get() while event != StopEvent: # 如果任务未全部完成: # 1、解开任务包 func, arguments, callback = event # 2、开始执行任务 try: result = func(*arguments) success = True # 状态标识:任务正确执行 except Exception as e: success = False # 状态标识:任务未正确执行 result = None # 3、根据任务执行状态和返回值,启动回调函数 if callback is not None: try: callback(success, result) except Exception as e: pass # 4、完成任务,当前线程添加至空闲列表——》取新任务——》空闲列表中删除当前线程 with self.worker_state(self.free_list, current_thread): if self.terminal: # 如果需要立即结束线程:不到queue中提取新任务,新任务直接设置为StopEvent event = StopEvent else: # 如果不需要立即结束进程:到queue中提取新任务执行 event = self.q.get() else: # 列表中删除当前线程(被删掉的进程在内存中,编程垃圾,等待回收) self.generate_list.remove(current_thread) def close(self): """ 执行完所有的任务后,所有线程停止 """ self.cancel = True full_size = len(self.generate_list) while full_size: self.q.put(StopEvent) full_size -= 1 def terminate(self): """ 无论是否还有任务,终止线程 """ self.terminal = True while self.generate_list: # 终止线程,清空队列 self.q.put(StopEvent) self.q.queue.clear() @contextlib.contextmanager def worker_state(self, state_list, worker_thread): """ 用于记录线程中正在等待的线程数 """ state_list.append(worker_thread) try: yield finally: state_list.remove(worker_thread) # How to use pool = ThreadPool(5) def callback(status, result): # status, execute action status # result, execute action return value pass def action(i): print(i) for i in range(30): ret = pool.run(action, (i,), callback) time.sleep(5) print(len(pool.generate_list), len(pool.free_list)) # pool.close() # pool.terminate()
浙公网安备 33010602011771号