python基础40 gil全局解释器所、互斥锁、线程队列、进程池和线程池、进程池爬取网页、携程、协程实现高并发程序
GIL全局解释器锁
Python在设计之初就考虑到要在住循环中,同时只有一个线程在运行
虽然Python解释器中可以‘运行’多个线程,但在任意时刻只有一个线程在解释其中运行
对Python解释器的访问有全局解释器来控制,正是这个锁能保证同一时刻只有一个个线程在运行
1.Python代码运行在解释器上,由解释器来翻译执行
2.Python解释器的种类有哪些?
cpython、ipython、pypy、jython等
3.GIL全局解释器锁在于cpython解释器中
4.起一个垃圾回收线程,再起一个正常执行代码的线程,当垃圾回收线程还没收回完毕,其他线程有可能抢夺资源,这种情况在Python设计之初就不允许的
5.市面上目前绝大多数(95%)都使用的是cpython解释器
6.python在设计之处,就在python解释器之上加了一把锁(GIL锁),加这个锁的目的是:同一时刻只能有一个线程执行,不能同时有多个线程执行,如果开了多个线程,那么,线程要想有执行权限,必须先拿到这把锁(GIL锁)
只需要记住:同一时刻多个线程只能有一个线程在运行,其他线程都处于等待状态
理解记忆部分
1.python有GIL锁的原因,同一个进程下多个线程实际同一时刻,只有一个线程在执行
2.只有在python上开进程多,其他语言一般不开多进程,只开多线程就够了
3.cpython解释器开多线程不能利用多核优势,只有开多进程才能够利用多核优势,其他语言不存在这个问题
4.8核CPU电脑,充分利用8核,至少起八个线程(8个线程不在同一个进程中),8条线程全是计算机,计算机cpu使用率是100%
5.如果不存在GIL锁,一个进程下开八个线程,它能够充分利用CPU资源,跑满cpu
6.cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了》》我们不能有8个核,但我现在只能用1核,》》开启多进程》》每个进程下开启的线程,可以被多个cpu调度执行
7. cpython解释器:io密集型使用多线程,计算密集型使用多进程
io密集型,遇到io操作会切换cpu,假设你开了8个线程,8个线程都有io操作---》io操作不消耗cpu---》一段时间内看上去,其实8个线程都执行了, 选多线程好一些
计算密集型,消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没执行,所以我们开8个进程,每个进程有一个线程,8个进程下的线程会被8个cpu执行,从而效率高
计算密集型选多进程好一些,在其他语言中,都是选择多线程,而不选择多进程
互斥锁
from threading import Thread, Lock n = 10 import time def task(lock): lock.acquire() # 只要有一个线程进来了,其他线程都要等着 global n # n -= 1 temp = n time.sleep(0.1) n = temp - 1 lock.release() if __name__ == '__main__': lock = Lock() ll = [] for i in range(10): t = Thread(target=task, args=(lock, )) t.start() ll.append(t) for j in ll: j.join() print('n:', n)
线程队列
进程之间的数据是隔离的,所以,我们使用了队列来实现了进程之间的通信
线程之间的数据是共享的,那么,我们为什么还是用队列?
队列底层:管道+锁
锁就是为了保证数据的安全,线程内部使用队列,也是为了保证线程里的数据安全
进程Queue用于父进程与子进程(或同一父进程中多个子进程)间数据传递
python自己的多个进程间交换数据或者与其他语言(如Java)进程queue就无能为力
queue.Queue 的缺点是它的实现涉及到多个锁和条件变量,因此可能会影响性能和内存效率。
只要加锁,必会影响性能和效率!但是好处就是保证数据的安全
线程队列的使用
1.先进先出
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get())
2.先进后出
Lifo: last input first output q=queue.LifoQueue() # 得到一个对象,队列 q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get())
3.优先级队列
import queue q = queue.PriorityQueue() # put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put(('a', 'a')) q.put(('b', 'b')) q.put(('c', 'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c')
进程池和线程池
池子:容器、盛放多个元素值
进程池:存放多个进程
线程池:存放多个线程
进程池:提前定义一个池子,里面放很多个进程,只需要往池子里面丢任务即可,有这个池子里面的任意一个进程来执行任务.
线程池:提前定义一个池子,里面放很多个线程,只需要往池子里面丢任务即可,有这个池子里面的任意一个线程来执行任务
例子:
def task(n, m): return n + m # 1+2 def task1(): # ... return { 'username':'kevin', 'password':123 } from multiprocessing import Process from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def callback(res): print(res.result()) def callback1(res): print(res.result()) print(res.result()['username']) if __name__ == '__main__': """开一个进程池,提前放进去多个进程""" # max_workers pool = ThreadPoolExecutor(2) # 池子里面有5个工作者,其实就是实例化了5个进程 # 有了进程池,我们现在往池子里面丢任务 # pool.submit(task, 1, 2) # 进程池要主动调一个回调函数,来把结果给到我们 # 回调函数需要我们自己提前写好 pool.submit(task, n=1, m=2).add_done_callback(callback) # pool.submit(task1).add_done_callback(callback1) pool.shutdown() # join + close print(123)
线程池网页爬取
import requests from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def get_page(url): res = requests.get(url) # 爬取网页 name = url.rsplit('/')[-1] + '.html' return {'name': name, 'text': res.content} def call_back(fut): print(fut.result()['name']) with open(fut.result()['name'], 'wb') as f: f.write(fut.result()['text']) if __name__ == '__main__': pool = ThreadPoolExecutor(2) urls = ['http://www.baidu.com', 'http://www.cnblogs.com', 'http://www.taobao.com'] for url in urls: pool.submit(get_page, url).add_done_callback(call_back)
协程
进程:进程解决高并发问题
线程:解决高并发问题
协程:它是单线程下的并发,它是程序员级别的,我们来控制如何切换,什么时候切换
进程和线程是操作系统来控制的,我们控制不了
协程是一种用户态(程序员)的轻量级线程,即协程是由用户程序自己控制调度的。
进程的开销 >>>>>> 线程的开销 >>>>>> 协程的开销
# 协程的使用需要借助于第三方模块gevent模块
必须先安装: pip install gevent
服务端: from gevent import monkey; monkey.patch_all() import gevent from socket import socket # from multiprocessing import Process from threading import Thread def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break print(data) conn.send(data.upper()) except Exception as e: print(e) conn.close() def server(ip, port): server = socket() server.bind((ip, port)) server.listen(5) while True: conn, addr = server.accept() # t=Process(target=talk,args=(conn,)) # t=Thread(target=talk,args=(conn,)) # t.start() gevent.spawn(talk, conn) if __name__ == '__main__': g1 = gevent.spawn(server, '127.0.0.1', 8080) g1.join() 客户端: import socket from threading import current_thread, Thread def socket_client(): cli = socket.socket() cli.connect(('127.0.0.1', 8080)) while True: ss = '%s say hello' % current_thread().getName() cli.send(ss.encode('utf-8')) data = cli.recv(1024) print(data) for i in range(5000): t = Thread(target=socket_client) t.start()
协程实现高并发程序
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY