进程
cpu要执行多任务,出现了进程;
进程是一个程序需要运行所需的资源的集合。
每个进程数据是独立的
每个进程里至少有一个线程。
进程里可以有多个线程
线程数据共享的
一个进程的多个线程可以充分利用多核cpu,即可以跑在多核上。
mutliprocessing
pipe
Queue
上面两个实现的是进程间的数据传递,通讯。
manager:实现了多进程间的数据共享
进程间共享数据的代价是高昂的,所以要尽量避免进程间的数据共享
线程间的数据本来就是共享的,
线程要修改同一份数据,必须加锁,互斥锁mutex。
event
线程间交互。
生产者消费者模型
解耦
提高程序的运行效率
queue
FIFO:排队
LIFO:超市里的菜
优先级queue:银行里金卡
适用场景
线程:IO密集型,socket 爬虫,web
进程:cpu运算密集型,金融分析
协程
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的好处:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
8核cpu,开8个进程,每个进程开上万线程,代表1颗cpu可以实现8万并发
单线程下的高并发
Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
from greenlet import greenlet import time def test1(): print(12) time.sleep(1) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()
以上代码运行结果
12 56 34 78
并没有解决遇到IO自动切换。
Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
import gevent def func1(): print('\033[31;1m李闯在跟海涛搞...\033[0m') gevent.sleep(4) print('\033[31;1m李闯又回去继续跟海涛搞...\033[0m') def func2(): gevent.sleep(0.5) print('\033[32;1m李闯切换到了跟海龙搞...\033[0m') gevent.sleep(2) print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m') def func3(): print("33333") gevent.sleep(1) print("4444") gevent.joinall([ gevent.spawn(func1), gevent.spawn(func2), gevent.spawn(func3), ])
以上代码运行结果
李闯在跟海涛搞... 33333 李闯切换到了跟海龙搞... 4444 李闯搞完了海涛,回来继续跟海龙搞... 李闯又回去继续跟海涛搞...
同步与异步的性能区别
import gevent def task(pid): """ Some non-deterministic task """ gevent.sleep(0.5) print('Task %s done' % pid) def synchronous(): for i in range(1, 10): task(i) def asynchronous(): #threads = [gevent.spawn(task, i) for i in range(10)] threads = [] for i in range(10): threads.append(gevent.spawn(task,i)) gevent.joinall(threads) print('Synchronous:') synchronous() print('Asynchronous:') asynchronous()
以上代码运行结果
Synchronous: Task 1 done Task 2 done Task 3 done Task 4 done Task 5 done Task 6 done Task 7 done Task 8 done Task 9 done Asynchronous: Task 0 done Task 9 done Task 8 done Task 7 done Task 6 done Task 5 done Task 4 done Task 3 done Task 2 done Task 1 done
遇到IO阻塞时会自动切换任务
import gevent from gevent import monkey #将url里面所有的操作改成异步的模式,即不堵塞的模式 monkey.patch_all() from urllib.request import urlopen import time def pa_web_page(url): print("GET url",url) req = urlopen(url) data =req.read() # print(data) print('%d bytes received from %s.' % (len(data), url)) t_start = time.time() pa_web_page("http://www.autohome.com.cn/beijing/") pa_web_page("http://www.xiaohuar.com/") print("time cost:",time.time()-t_start) t2_start = time.time() gevent.joinall([ #gevent.spawn(pa_web_page, 'https://www.python.org/'), gevent.spawn(pa_web_page, 'http://www.autohome.com.cn/beijing/'), gevent.spawn(pa_web_page, 'http://www.xiaohuar.com/'), #gevent.spawn(pa_web_page, 'https://github.com/'), ]) print("time cost t2:",time.time()-t2_start)
以上代码执行结果
1077321 bytes received from http://www.autohome.com.cn/beijing/. GET url http://www.xiaohuar.com/ 49847 bytes received from http://www.xiaohuar.com/. time cost: 0.4413177967071533 GET url http://www.autohome.com.cn/beijing/ GET url http://www.xiaohuar.com/ 49847 bytes received from http://www.xiaohuar.com/. 1077321 bytes received from http://www.autohome.com.cn/beijing/. time cost t2: 0.23272156715393066
通过gevent实现单线程下的多socket并发
server side
import sys import socket import time import gevent from gevent import monkey monkey.patch_all() def server(port): s = socket.socket() s.bind(('0.0.0.0', port)) s.listen(500) while True: cli, addr = s.accept() gevent.spawn(handle_request, cli) def handle_request(conn): try: while True: data = conn.recv(1024) print("recv:", data) conn.send(data) if not data: conn.shutdown(socket.SHUT_WR) except Exception as ex: print(ex) finally: conn.close() if __name__ == '__main__': server(8001)
client side
import socket HOST = 'localhost' # The remote host PORT = 8001 # The same port as used by the server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True: msg = bytes(input(">>:"), encoding="utf8") s.sendall(msg) data = s.recv(1024) # print(data) print('Received', repr(data)) s.close()
用threading并发100个连接
import socket import threading def sock_conn(): client = socket.socket() client.connect(("localhost",8001)) count = 0 while True: #msg = input(">>:").strip() #if len(msg) == 0:continue client.send( ("hello %s" %count).encode("utf-8")) data = client.recv(1024) print("[%s]recv from server:" % threading.get_ident(),data.decode()) #结果 count +=1 client.close() for i in range(5000): t = threading.Thread(target=sock_conn) t.start()