第二课、Python多线程
一、课程介绍
1.1 课程概要
章节概要
- 进程、线程与并发
- 对多核的利用
- 实现一个线程
- 线程之间的通信
- 线程的调度和优化
1.2 为什么要学习多线程
(线程)使用场景
- 快速高效的爬虫程序
- 多用户同时访问的web服务
- 电商秒杀、抢购活动
- 物联网传感器监控服务器
线程vs进程vs协程
重要性
- 跳槽、面试、决定薪资高度
- 解决“效率”问题
- Python的GIL导致的系列问题
- 通常会混合使用(多进程+协程)
二、线程的实现及相关操作
2.1 线程的介绍
进程
- 是一个执行中的程序
- 每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据
- 操作系统管理及上所有进程的执行,并为这些进程合理的分配时间
- 进程也可通过派生(fork或spawn)新的进程来执行其他任务
线程
- 在同一个进程下执行,并共享相同的上下文
- 一个进程中的各个线程与主线程共享同一片数据空间
- 线程包括开始、执行顺序和结束三部分
- 它可以被抢占(中断)和临时挂起(也成为睡眠)————让步
- 一般是以并发方式执行
并发
- 是一种属性——程序、算法或问题的属性
- 并行是指并发问题的可能方法之一
- 如果两个事件互不影响,则两个事件是并发的
2.2 对多核的利用及GIL概念
对多核的利用
- 单核CPU系统中,不存在真正的并发
- GIL——全局解释器锁
- GIL只是强制在任何时候只有一个线程可以执行Python代码
- I/O密集型应用与CPU密集型应用
GIL执行顺序
- 设置GIL
- 切换进一个线程去运行
- 执行下面操作之一:
- 指定数量的字节码指令
- 线程主动让出控制权(可以调用time.sleep(0)来完成)
- 把线程设置回睡眠状态(切换出线程)
- 解锁GIL
- 重复上述步骤
2.3 线程的两种实现方式
实现一个线程
- 用threading模块代替thread模块
- 用threading.Tread创建线程
- start()启动线程
- jion()挂起线程
threading模块的对象
对象 | 描述 |
Thread | 表示一个执行线程的对象 |
Lock | 锁原语对象(和thread模块中的锁一样) |
RLock | 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁) |
Condition | 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据值 |
Event | 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活 |
Semaphore | 为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞 |
BoundedSemap hore | 与Semaphore相似,不过它不允许超过初始值 |
Timer | 与Thread相似,不过它要再运行前等待一段时间 |
Barrier | 创建一个”障碍“,必须达到指定数量的线程后才可以继续 |
Thread对象数据属性
属性 | 描述 |
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标志,表示这个线程是否是守护线程 |
Thread对象方法
属性 | 描述 |
__init__() | 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs |
start() | 开始执行该线程 |
run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
jion(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞 |
getName() | 返回线程名 |
setName(name) | 设定线程名 |
isAlivel/is_alive() | 布尔标志,表示这个线程是否还存活 |
isDaemon() | 如果是守护线程,则返回True;否则,返回False |
setDaemon() | 把线程的守护标志设定为布尔值daemonic(必须在线程start()之前调用) |
实现一个线程
1 import threading 2 import time 3 4 5 def loop(): 6 """ 新的线程执行的代码 """ 7 n = 0 8 while n < 5: 9 print(n) 10 now_thread = threading.current_thread() 11 print('now thread name : {0}'.format(now_thread.name)) 12 time.sleep(1) 13 n += 1 14 15 16 def use_thread(): 17 """ 使用线程来实现 """ 18 # 当前正在执行的线程名称 19 now_thread = threading.current_thread() 20 print('now thread name : {0}'.format(now_thread)) 21 t = threading.Thread(target=loop, name='loop_thread') 22 # 启动线程 23 t.start() 24 # 挂起线程 25 t.join() 26 27 28 if __name__ =='__main__': 29 use_thread()
1 import threading 2 import time 3 4 5 class LoopThread(threading.Thread): 6 """ 自定义线程 """ 7 8 n = 0 9 10 def run(self): 11 while self.n < 5: 12 print(self.n) 13 now_thread = threading.current_thread() 14 print('now thread name : {0}'.format(now_thread.name)) 15 time.sleep(1) 16 self.n += 1 17 18 19 if __name__ == '__main__': 20 # 当前正在执行的线程名称 21 now_thread = threading.current_thread() 22 print('now thread name : {0}'.format(now_thread.name)) 23 t = LoopThread(name='loop_thread_oop') 24 t.start() 25 t.join()
2.4 多线程并发问题
实现多个线程
- 最后为什么不是0?
1 import threading 2 import time 3 4 5 6 # 我的银行账户 7 balance = 0 8 9 10 def change_it(n): 11 """改变我的余额""" 12 global balance 13 balance = balance + n 14 time.sleep(2) 15 balance = balance - n 16 time.sleep(1) 17 print('--------->{0}'.format(n)) 18 19 20 class ChangeBalanceThread(threading.Thread): 21 """ 22 改变银行余额的线程 23 """ 24 25 def __init__(self, num, *args, **kwargs): 26 super().__init__(*args, **kwargs) 27 self.num = num 28 29 def run(self): 30 for i in range(1000): 31 change_it(self.num) 32 33 34 if __name__ == '__main__': 35 t1 = ChangeBalanceThread(5) 36 t2 = ChangeBalanceThread(8) 37 t1.start() 38 t2.start() 39 t1.join() 40 t2.join() 41 print('the last: {0}'.format(balance))
2.5 多线程中的锁
多线程中的锁实现
- Lock()
- Rlock()
- Condition()
1 import threading 2 import time 3 4 5 # 获得一把锁 6 my_lock = threading.Lock() 7 your_lock = threading.RLock() 8 9 # 我的银行账户 10 balance = 0 11 12 13 def change_it(n): 14 """ 改变我的余额 """ 15 global balance 16 17 # 方式一,使用with 18 with your_lock: 19 balance = balance + n 20 time.sleep(2) 21 balance = balance - n 22 time.sleep(1) 23 print('-N---> {0}; balance: {1}'.format(n, balance)) 24 25 # 方式二 26 # try: 27 # print('start lock') 28 # # 添加锁 29 # your_lock.acquire() 30 # print('locked one ') 31 # # 资源已经被锁住了,不能重复锁定, 产生死锁 32 # your_lock.acquire() 33 # print('locked two') 34 # balance = balance + n 35 # time.sleep(2) 36 # balance = balance - n 37 # time.sleep(1) 38 # print('-N---> {0}; balance: {1}'.format(n, balance)) 39 # finally: 40 # # 释放掉锁 41 # your_lock.release() 42 # your_lock.release() 43 44 45 class ChangeBalanceThread(threading.Thread): 46 """ 47 改变银行余额的线程 48 """ 49 50 def __init__(self, num, *args, **kwargs): 51 super().__init__(*args, **kwargs) 52 self.num = num 53 54 def run(self): 55 for i in range(100): 56 change_it(self.num) 57 58 59 if __name__ == '__main__': 60 t1 = ChangeBalanceThread(5) 61 t2 = ChangeBalanceThread(8) 62 t1.start() 63 t2.start() 64 t1.join() 65 t2.join() 66 print('the last: {0}'.format(balance))
2.6 线程的调度和优化
线程的调度和优化
1 import time 2 import threading 3 from concurrent.futures import ThreadPoolExecutor 4 from multiprocessing.dummy import Pool 5 6 7 def run(n): 8 """ 线程要做的事情 """ 9 time.sleep(2) 10 print(threading.current_thread().name, n) 11 12 13 def main(): 14 """ 使用传统的方法来做任务 """ 15 t1 = time.time() 16 for n in range(100): 17 run(n) 18 print(time.time() - t1) 19 20 21 def main_use_thread(): 22 """ 使用线程优化任务 """ 23 # 资源有限,最多只能跑10个线程 24 t1 = time.time() 25 ls = [] 26 for count in range(10): 27 for i in range(10): 28 t = threading.Thread(target=run, args=(i,)) 29 ls.append(t) 30 t.start() 31 32 for l in ls: 33 l.join() 34 print(time.time() - t1) 35 36 37 def main_use_pool(): 38 """ 使用线程池来优化 """ 39 t1 = time.time() 40 n_list = range(100) 41 pool = Pool(10) 42 pool.map(run, n_list) 43 pool.close() 44 pool.join() 45 print(time.time() - t1) 46 47 48 def main_use_executor(): 49 """ 使用 ThreadPoolExecutor 来优化""" 50 t1 = time.time() 51 n_list = range(100) 52 with ThreadPoolExecutor(max_workers=10) as executor: 53 executor.map(run, n_list) 54 print(time.time() - t1) 55 56 57 if __name__ == '__main__': 58 # main() 59 # main_use_thread() 60 # main_use_pool() 61 main_use_executor()
三、进程介绍
3.1 进程介绍
进程
- 是一个执行中的程序
- 每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据
- 操作系统管理其上所有进程的执行,并为这些进程合理的分配时间
- 进程也可通过派生(fork或spawn)新的进程来执行其他任务
章节概要
- 进程模块介绍
- 进程的实现
- 进程之间的通信
- 使用进程池
进程模块介绍
- 我们使用multiprocessing实现多进程代码
- 用multiprocessing.Process创建进程
- start()启动进程
- join()挂起进程
- os.getpid()获得进程的ID
3.2 进程的实现
进程的实现
1 import os 2 import time 3 from multiprocessing import Process 4 5 6 def do_sth(name): 7 """ 8 进程要做的事情 9 :param name: str 进程的名称 10 """ 11 print('进程的名称:{0}, pid: {1}'.format(name, os.getpid())) 12 time.sleep(150) 13 print('进程要做的事情') 14 15 16 class MyProcess(Process): 17 18 def __init__(self, name, *args, **kwargs): 19 self.my_name = name 20 # print(self.name) 21 super().__init__(*args, **kwargs) 22 print(self.name) 23 24 def run(self): 25 print('MyProcess进程的名称:{0}, pid: {1}'.format( 26 self.my_name, os.getpid())) 27 time.sleep(150) 28 print('MyProcess进程要做的事情') 29 30 31 if __name__ == '__main__': 32 # p = Process(target=do_sth, args=('my process', )) 33 p = MyProcess('my process class') 34 # 启动进程 35 p.start() 36 # 挂起进程 37 p.join()
3.3 进程之间的通信
进程之间的通信
- 通过Queue、Pipes等实现进程之间的通信
1 from multiprocessing import Process, Queue, current_process 2 3 import random 4 import time 5 6 7 class WriteProcess(Process): 8 """ 写的进程 """ 9 def __init__(self, q, *args, **kwargs): 10 self.q = q 11 super().__init__(*args, **kwargs) 12 13 def run(self): 14 """ 实现进程的业务逻辑 """ 15 # 要写的内容 16 ls = ( 17 "第一行内容", 18 "第2行内容", 19 "第3行内容", 20 "第4行内容", 21 ) 22 for line in ls: 23 print('写入内容: {0} - {1}'.format(line, current_process().name)) 24 self.q.put(line) 25 # 每写入一次,休息1-5秒 26 time.sleep(random.randint(1, 5)) 27 28 29 class ReadProcess(Process): 30 """ 读取内容进程 """ 31 def __init__(self, q, *args, **kwargs): 32 self.q = q 33 super().__init__(*args, **kwargs) 34 35 def run(self): 36 while True: 37 content = self.q.get() 38 print('读取到的内容:{0} - {1}'.format(content, self.name)) 39 40 41 if __name__ == '__main__': 42 # 通过Queue共享数据 43 q = Queue() 44 # 写入内容的进程 45 t_write = WriteProcess(q) 46 t_write.start() 47 # 读取进程启动 48 t_read = ReadProcess(q) 49 t_read.start() 50 51 t_write.join() 52 # t_read.join() 53 54 # 因为读的进程是死循环,无法等待其结束,只能强制终止 55 t_read.terminate()
3.4 多进程中的锁
多进程中的锁
- Lock()
- Rlock()
- Condition()
1 import random 2 from multiprocessing import Process, Lock, RLock 3 4 import time 5 6 7 class WriteProcess(Process): 8 """ 写入文件 """ 9 10 def __init__(self, file_name, num, lock, *args, **kwargs): 11 # 文件的名称 12 self.file_name = file_name 13 self.num = num 14 # 锁对象 15 self.lock = lock 16 super().__init__(*args, **kwargs) 17 18 def run(self): 19 """ 写入文件的主要业务逻辑 """ 20 with self.lock: 21 # try: 22 # # 添加锁 23 # self.lock.acquire() 24 # print('locked') 25 # self.lock.acquire() 26 # print('relocked') 27 for i in range(5): 28 content = '现在是: {0} : {1} - {2} \n'.format( 29 self.name, 30 self.pid, 31 self.num 32 ) 33 with open(self.file_name, 'a+', encoding='utf-8') as f: 34 f.write(content) 35 time.sleep(random.randint(1, 5)) 36 print(content) 37 # finally: 38 # # 释放锁 39 # self.lock.release() 40 # self.lock.release() 41 42 43 if __name__ == '__main__': 44 file_name = 'test.txt' 45 # 锁的对象 46 lock = RLock() 47 for x in range(5): 48 p = WriteProcess(file_name, x, lock) 49 p.start()
3.5 使用进程池
使用进程池
1 import random 2 from multiprocessing import current_process, Pool 3 4 import time 5 6 7 def run(file_name, num): 8 """ 9 进程执行的业务逻辑 10 往文件中写入数据 11 :param file_name: str 文件名称 12 :param num: int 写入的数字 13 :return: str 写入的结果 14 """ 15 with open(file_name, 'a+', encoding='utf-8') as f: 16 # 当前的进程 17 now_process = current_process() 18 # 写入的内容 19 conent = '{0} - {1}- {2}'.format( 20 now_process.name, 21 now_process.pid, 22 num 23 ) 24 f.write(conent) 25 f.write('\n') 26 # 写完之后随机休息1-5秒 27 time.sleep(random.randint(1, 5)) 28 print(conent) 29 return 'ok' 30 31 32 if __name__ == '__main__': 33 file_name = 'test_pool.txt' 34 # 进程池 35 pool = Pool(2) 36 rest_list = [] 37 for i in range(20): 38 # 同步添加任务 39 # rest = pool.apply(run, args=(file_name, i)) 40 rest = pool.apply_async(run, args=(file_name, i)) 41 rest_list.append(rest) 42 print('{0}--- {1}'.format(i, rest)) 43 # 关闭池子 44 pool.close() 45 pool.join() 46 # 查看异步执行的结果 47 print(rest_list[0].get())
四、协程的实现及通信
4.1 协程介绍
章节概要
- 协程介绍
- 协程的实现
- 协程之间的通信
协程介绍
- 协程就是协同多任务
- 协程在一个进程或者是一个线程中执行
- 不需要锁机制
- 对多核CPU的利用——多进程+协程
4.2 协程的实现
协程的实现
- python3.5以前
- 使用生成器(yield)来实现
1 def count_down(n): 2 """ 倒计时效果 """ 3 while n > 0: 4 yield n 5 n -= 1 6 7 8 def yield_test(): 9 """ 实现协程函数 """ 10 while True: 11 n = (yield ) 12 print(n) 13 14 15 if __name__ == '__main__': 16 # rest = count_down(5) 17 # print(next(rest)) 18 # print(next(rest)) 19 # print(next(rest)) 20 # print(next(rest)) 21 # print(next(rest)) 22 rest = yield_test() 23 next(rest) 24 rest.send('6666') 25 rest.send('6666')
- python3.5以后
- 使用async和await关键字实现
async关键字
- 定义特殊函数
async def async_f():
pass
- 当被调用时,不执行里面的代码,而是返回一个协程对象
- 在事件循环中调度其执行前,协程对象不执行任何操作
await关键字
- 等待协程执行完成
- 当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出,以便loop调用其他的协程
asyncio模块
- get_event_loop()获得事件循环队列
- run_until_complete()注册任务到队列
- 在事件循环中调度其执行前,协程对象不执行任何操作
- asyncio模块用于事件循环
1 import asyncio 2 3 4 async def do_sth(x): 5 """ 定义协程函数 """ 6 print('等待中: {0}'.format(x)) 7 await asyncio.sleep(x) 8 9 # 判断是否为协程函数 10 print(asyncio.iscoroutinefunction(do_sth)) 11 12 coroutine = do_sth(5) 13 # 事件的循环队列 14 loop = asyncio.get_event_loop() 15 # 注册任务 16 task = loop.create_task(coroutine) 17 print(task) 18 # 等待协程任务执行结束 19 loop.run_until_complete(task) 20 print(task)
4.3 协程通信之嵌套调用
协程之间的数据通信
- 嵌套调用
1 import asyncio 2 3 4 async def compute(x, y): 5 print('计算x +y => {0}+{1}'.format(x, y)) 6 await asyncio.sleep(3) 7 return x + y 8 9 10 async def get_sum(x, y): 11 rest = await compute(x, y) 12 print('{0} + {1} = {2}'.format(x, y, rest)) 13 14 # 拿到事件循环 15 loop = asyncio.get_event_loop() 16 loop.run_until_complete(get_sum(1, 2)) 17 loop.close()
4.4 协程通信之队列
协程之间的数据通信
- 队列
1 # 1. 定义一个队列 2 # 2. 让两个协程来进行通信 3 # 3. 让其中一个协程往队列中写入数据 4 # 4. 让另一个协程从队列中删除数据 5 6 import asyncio 7 import random 8 9 10 async def add(store, name): 11 """ 12 写入数据到队列 13 :param store: 队列的对象 14 :return: 15 """ 16 for i in range(5): 17 # 往队列中添加数字 18 num = '{0} - {1}'.format(name, i) 19 await asyncio.sleep(random.randint(1, 5)) 20 await store.put(i) 21 print('{2} add one ... {0}, size: {1}'.format( 22 num, store.qsize(), name)) 23 24 25 async def reduce(store): 26 """ 27 从队列中删除数据 28 :param store: 29 :return: 30 """ 31 for i in range(10): 32 rest = await store.get() 33 print(' reduce one.. {0}, size: {1}'.format(rest, store.qsize())) 34 35 36 if __name__ == '__main__': 37 # 准备一个队列 38 store = asyncio.Queue(maxsize=5) 39 a1 = add(store, 'a1') 40 a2 = add(store, 'a2') 41 r1 = reduce(store) 42 43 # 添加到事件队列 44 loop = asyncio.get_event_loop() 45 loop.run_until_complete(asyncio.gather(a1, a2, r1)) 46 loop.close()