一、异步阻塞
1、并没有按照执行顺序等待结果
2、而是所有的任务都在异步执行着
3、但是我要的结果又不知道谁的结果先来,谁先结束我就先取谁的结果
很明显的异步,大家都相互执行着(异步过程),谁先结束我就先拿谁的结果,而我等待的过程就是一个阻塞过程,整体就是一个异步阻塞。
使用生产者消费者模型举例:
import requests from multiprocessing import Process, Queue url = [ 'https://www.baidu.com', 'https://www.taobao.com', 'https://www.jd.com', 'https://i.cnblogs.com' ] def producer(name, url, q): # 生产者负责爬取网页,获取状态码放进列队 ret = requests.get(url) q.put((name, ret.status_code)) def consumer(q): # 消费者 while True: conn = q.get() if conn: print(conn) else:break if __name__ == '__main__': q = Queue() p_list = [] for i in url: p = Process(target=producer, args=(i, i, q)) p.start() p_list.append(p) Process(target=consumer, args=(q,)).start() # 输出 ('https://www.jd.com', 200) ('https://www.baidu.com', 200) ('https://www.taobao.com', 200) ('https://i.cnblogs.com', 200)
二、Manager模块进程间数据共享(了解)
进程之间是可以实现数据共享的,通过Manager这个类就可以实现了,只不过呢实现这个数据共享的代价是非常大的,即需要对这个共享的数据进行加锁,又要去操作许多不必要的操作。
一般情况下我们不使用这种方法,使用进程的原因就是因为进程之间数据隔离,如果非要让大量数据共享,就证明这个场景不适合用进程去解决
from multiprocessing import Process, Manager, Lock def change_dic(dic, lock): with lock: # 使用数据共享需要加锁才能保证数据的安全,和抢票例子一样 dic['count'] -= 1 if __name__ == '__main__': m = Manager() lock = Lock() dic = m.dict({'count': 100}) # 可以是字典,列表 p_l = [] for i in range(100): p = Process(target=change_dic, args=(dic, lock)) p.start() p_l.append(p) for p in p_l: p.join() print(dic) # 输出 {'count': 0}
三、线程
进程:数据隔离,资源分配的最小单位,可以利用多核,由操作系统调度,数据不安全,开启关闭切换时间开销大
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是CPU的执行单位。
线程:数据共享,操作系统调度的最小单位,可以利用多核,由操作系统调度,数据不安全,开启关闭切换时间开销小
能被操作系统调度(给CPU执行)的最小单位,同一个进程中的多个线程同时能被CPU执行,
线程与进程的区别:
1、线程共享创建它的进程的地址空间;进程具有自己的地址空间。
2、线程可以直接访问其进程的数据;进程具有父进程数据的副本。
3、线程可以直接与进程中的其他线程通信;进程必须使用进程间通信与同级进程进行通信。
4、新线程很容易创建;新进程需要复制父进程。
5、线程可以对同一进程的线程使用相当大的控制权;进程只能控制子进程。
6、对主线程的更改可能会影响该进程对其他线程的行为;对父进程的修改不会影响子进程。
线程的特点:
1、轻型实体:线程中的实体基本不拥有系统资源。
2、独立调度和分派的基本单位:由于线程很轻,故线程的切换非常迅速且开销小(同一进程中的)。
3、共享进程资源:线程在同一进程中的各个线程,都可以共享该进程拥有的所有资源。
4、可并发执行:在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行
全局解释器锁 GIL (global interpreter lock)
在CPython中的多线程中,垃圾回收机制(gc)理解为相当于一个线程,它使用了引用计数 + 分代回收,来对变量中的引用计数为0的变量进行回收,但是如果在多线程中当CPU同时对多个线程的变量进行操作,gc 都要兼顾对每条线程的变量做引用计数,这样的话还是会造成和抢票例子一样的效果,为此诞生了一个全局解释器锁,它让每条线程中的变量要被CPU操作时,同时并且只有一条线程能被CPU操作。
全局解释器锁的出现主要是为了完成gc的回收机制,对不同线程的引用计数的变化记录更加精准
但是全局解释器锁导致了同一个进程中的多个线程只能有一个线程真正被CPU执行
节省的是io操作时间,而不是CPU计算的时间,因为CPU的计算速度非常快,大部分情况下,我们没有办法把一条进程中所有的io操作规避掉。
四、threading模块
thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。
thread模块不支持守护线程,当主线进程退出时,所有的子线程不论他们是否还在工作,都会被强行退出。而threading模块支持守护线程。
线程的创建Threading. Thread类
multiprocessing模块完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。
current_thread()获取当前所在的线程对象,current_thread(). ident 通过 ident 可以获取线程id
线程是不能从外部terminale
所有的子线程只能是自己执行完代码之后关闭
enumerate 列表,存储了所有活着的线程对象,包括主线程
active_count 数字,存储了所有活着的线程个数
from threading import Thread, current_thread, active_count, enumerate import time def func(i): time.sleep(1) print(f'这是线程func{i},线程id={current_thread().ident}') t_list = [] for i in range(5): t = Thread(target=func, args=(i,)) t.start() t_list.append(t) print(enumerate()) print(f'活着的线程个数{active_count()}') for th in t_list: th.join() print('所有的线程都执行完了!') # 输出 [<_MainThread(MainThread, started 8600)>, <Thread(Thread-1, started 18832)>,...] 活着的线程个数6 这是线程func3,线程id=3148 这是线程func1,线程id=2296 这是线程func0,线程id=18832 这是线程func2,线程id=18644 这是线程func4,线程id=12072 所有的线程都执行完了!
使用面向对象创建线程
from threading import Thread, current_thread class MyThread(Thread): def __init__(self,i): self.i = i super().__init__() def run(self): print(f'这是线程{self.i},线程号={current_thread().ident}') t = MyThread(1) t.start() # 开启线程,在线程中执行run方法。 # 输出 这是线程1,线程号=10160
线程之间的数据共享
修改成功说明进程的数据是共享给线程的。
from threading import Thread count = 100 def func(): global count count -= 1 t_list = [] for i in range(100): t = Thread(target=func) t.start() t_list.append(t) for th in t_list: th.join() print(f'所有的线程执行完了,count={count}') # 输出 所有的线程执行完了,count=0