本文共 6688 字,大约阅读时间需要 22 分钟。
Python自己没有这玩意,Python中调用的操作系统的线程和进程。
计算密集型操作:效率低,Python内置的一个全局解释器锁,锁的作用就是保证同一时刻一个进程中只有一个线程可以被cpu调度,多线程无法利用多核优势,可以通过多进程方式解决,但是比较浪费资源。
IO操作:效率高计算密集型操作:效率高(浪费资源),不得已而为之。
IO操作:效率高(浪费资源)Python语言的创始人在开发这门语言时,目的快速把语言开发出来,如果加上GIL锁(C语言加锁),切换时按照100条字节指令来进行线程间的切换。
GIL锁,全局解释器锁。用于限制一个进程中同一时刻只有一个线程被cpu调度。
扩展:默认GIL锁在执行100个cpu指令(过期时间)。查看GIL切换的指令个数import sysv1 = sys。getcheckinterval()print(v1)
由于线程是cpu工作的最小单元,创建线程可以利用多核优势实现并行操作(Java/C#)。
注意:线程是为了工作。进程和进程之间做数据隔离(Java/C#)。
注意:进程是为了提供环境让线程工作。
进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程小很多,同时创建一个线程的开销比进程要小很多。
线程之间的通信更方便,同一进程下的线程共享全局变量,静态变量,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
也正是由于GIL锁的原因:IO密集型操作可以使用多线程,计算密集型可以使用多进程。
不好。线程之间进行切换时,要做上下文管理。
不用一直等待的问题。
RLock可以多次加锁。
进程是cpu资源分配的最小单元,一个进程中可以有多个线程。
线程是cpu计算的最小单元。
对于Python来说他的进程和线程和其他语言有差异,是有GIL锁。
GIL锁保证一个进程中同一时刻只有一个线程被cpu调度。
注意:IO密集型操作可以使用多线程,计算密集型可以使用多进程。
协程,是由程序员创造出来的一个不是真实存在的东西。
协程,是微线程,对一个线程进程分片,使得线程在代码块之间进行来回切换执行,而不是在原来逐行执行。
检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)IO多路复用作用?
检测多个socket是否发生变化。
操作系统检测socket是否发生变化,有三种模式:
Python模块:
默认是阻塞,填在等待消息和连接
非阻塞,不等待。比如创建socket对某个地址进行connect、获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作。
如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可。异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知)。比如做爬虫中向某个地址baidu。com发送请求,当请求执行完成之后自执行回调函数。
协程也可以称为“微线程”,就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函执行代码...来回切换。
协程自己本身无法实现并发(甚至性能会降低)。
协程+IO切换性能提高。实现并发请求(一个线程100个请求)
import socket# 创建socketclient = socket.socket()# 将原来阻塞的位置变成非阻塞(报错)client.setblocking(False)# 百度创建连接: 阻塞try: # 执行了但报错了 client.connect(('www.baidu.com',80))except BlockingIOError as e: pass# 检测到已经连接成功# 问百度我要什么?client.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')# 我等着接收百度给我的回复chunk_list = []while True: # 将原来阻塞的位置变成非阻塞(报错) chunk = client.recv(8096) if not chunk: break chunk_list.append(chunk)body = b''.join(chunk_list)print(body.decode('utf-8'))
from gevent import monkey# 以后代码中遇到IO都会自动执行greenlet的switch进行切换monkey.patch_all() import requestsimport geventdef get_page1(url): ret = requests.get(url) print(url,ret.content)def get_page2(url): ret = requests.get(url) print(url,ret.content)def get_page3(url): ret = requests.get(url) print(url,ret.content)gevent.joinall([ gevent.spawn(get_page1, 'https://www.python.org/'), # 协程1 gevent.spawn(get_page2, 'https://www.yahoo.com/'), # 协程2 gevent.spawn(get_page3, 'https://github.com/'), # 协程3])
执行完某个人物后自动调用我给他的函数,非阻塞
import socketimport select# 百度创建连接:非阻塞client1 = socket.socket()client1.setblocking(False)try: client1.connect(('www.baidu.com', 80))except BlockingIOError as e: pass# 搜狗创建连接:非阻塞client2 = socket.socket()client2.setblocking(False)try: client2.connect(('www.sogou.com', 80))except BlockingIOError as e: pass# GitHub创建连接:非阻塞client3 = socket.socket()client3.setblocking(False)try: client3.connect(('www.github.com', 80))except BlockingIOError as e: pass# 创建socket列表:socket_listsocket_list = [client1, client2, client3]# 创建connect列表:conn_listconn_list = [client1, client2, client3]while True: rlist, wlist, elist = select.select(socket_list, conn_list, [], 0.005) # rlist中表示已近获取数据的socket对象 # wlist中表示已经连接成功的socket对象 # elist中表示出现错误的socket对象 for sk in wlist: if sk == client1: sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n') elif sk == client2: sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n') else: sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n') conn_list.remove(sk) for sk in rlist: chunk_list = [] while True: try: chunk = sk.recv(8096) if not chunk: break chunk_list.append(chunk) except BlockingIOError as e: break body = b''.join(chunk_list) # print(body.decode('utf-8')) print('------------>', body) sk.close() socket_list.remove(sk) if not socket_list: break
import socketimport selectclass Req(object): def __init__(self,sk,func): self.sock = sk self.func = func def fileno(self): return self.sock.fileno()class Nb(object): def __init__(self): self.conn_list = [] self.socket_list = [] def add(self,url,func): # 创建socket客户端 client = socket.socket() # 非阻塞 client.setblocking(False) try: # 创建连接 client.connect((url, 80)) # 异常处理 except BlockingIOError as e: pass obj = Req(client,func) # 连接列表 self.conn_list.append(obj) # socket列表 self.socket_list.append(obj) def run(self): while True: rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005) # wlist中表示已经连接成功的req对象 for sk in wlist: # 发生变换的req对象 sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n') self.conn_list.remove(sk) for sk in rlist: chunk_list = [] while True: try: chunk = sk.sock.recv(8096) if not chunk: break chunk_list.append(chunk) except BlockingIOError as e: break body = b''.join(chunk_list) # print(body.decode('utf-8')) sk.func(body) sk.sock.close() self.socket_list.remove(sk) if not self.socket_list: breakdef baidu_repsonse(body): print('百度下载结果:',body)def sogou_repsonse(body): print('搜狗下载结果:', body)def github_repsonse(body): print('GITHUB下载结果:', body)t1 = Nb()t1.add('www.baidu.com',baidu_repsonse)t1.add('www.sogou.com',sogou_repsonse)t1.add('www.github.com',oldboyedu_repsonse)t1.run()
如Twisted框架,scrapy框架(单线程完成并发)
转载地址:http://scgyz.baihongyu.com/