GIL全局解释器锁:
python解释器:
1.Cpython C ,
2.Jpython java ,
3.Ppython Python
1.GIL全局解释器锁:
- 翻译: 在同一个进程下开启的多线程,同一时刻只能有一个线程执行,因为Cpython的内存管理不是线程安全。
GIL全局解释器锁,本质上就是一把互斥锁,保证数据安全。
单个进程下的多个线程无法实现并发,但能实现并发
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
GIL全局解释器锁
本质上就是一把互斥锁,保证数据安全。
2、GIL全局解释器锁的优缺点:
1.优点:
保证数据的安全
2.缺点:
单个进程下,开启多个线程,牺牲执行效率,无法实现并行,只能实现并发。
- IO密集型, 多线程
- 计算密集型,多进程
代码演示
import time
from threading import Thread, current_thread
number = 100
def task():
global number
number2 = number
# time.sleep(1)
number = number2 - 1
print(number, current_thread().name)
for line in range(100):
t = Thread(target=task)
t.start()
#结果
99 Thread-1
98 Thread-2
.
.
.
1 Thread-99
0 Thread-100
import time
from threading import Thread
n = 100
def task():
global n
m = n
time.sleep(3)
n = m - 1
if __name__ == '__main__':
list1 = []
for line in range(10):
t = Thread(target=task)
t.start()
list1.append(t)
for t in list1:
t.join()
print(n)
# 结果:
99
多线程的作用:
站在两个角度去看问题:
四个任务, 计算密集型, 每个任务需要10s:
单核:
开启进程
消耗资源过大
4个进程: 40s
开启线程
消耗资源远小于进程
- 4个线程: 40s
多核:
开启进程
并行执行,效率比较高
4个进程: 10s
开启线程
并发执行,执行效率低.
4个线程: 40s
四个任务, IO密集型, 每个任务需要10s:
单核:
开启进程
3消耗资源过大
34个进程: 40s
开启线程
消耗资源远小于进程
4个线程: 40s
多核:
开启进程
并行执行,效率小于多线程,因为遇到IO会立马切换CPU的执行权限
4个进程: 40s + 开启进程消耗的额外时间
开启线程
并发执行,执行效率高于多进程
4个线程: 40s
rom threading import Thread
from multiprocessing import Process
import os
import time
# 计算密集型
def work1():
number = 0
for line in range(100000000):
number += 1
# IO密集型
def work2():
time.sleep(1)
if __name__ == '__main__'
# IO密集型
print(os.cpu_count()) # 6
# 开始时间
start_time = time.time()
list1 = []
for line in range(40):
# p = Process(target=work2) # 程序执行时间4.445072174072266
p = Thread(target=work2) # 程序执行时间1.009237289428711
list1.append(p)
p.start()
for p in list1:
p.join()
end_time = time.time()
print(f'程序执行时间{end_time - start_time}')
'''
在计算密集型的情况下:
使用多进程
在IO密集型的情况下:
使用多线程
高效执行多个进程,内多个IO密集型的程序:
使用 多进程 + 多线程
'''
在计算密集型的情况下:
使用多进程
在IO密集型的情况下:
使用多线程
高效执行多个进程,内多个IO密集型的程序:
使用 多进程 + 多线程
计算密集型: 多进程
计算密集型: 多进程
假设100份原材料同时到达工厂,聘请100个人同时制造,效率最高
IO密集型: 多线程
假设100份原材料,只有40份了,其他还在路上,聘请40个人同时制造。
死锁现象
from threading import Lock,Thread,current_thread
import time
mutex_a = Lock()
mutex_b = Lock()
class MyThread(thread):
#线程执行任务
def run(self)
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用户{self.name}强到锁a')
mutex_b.acquire()
print(f'用户{self.name}抢到锁b')
mutex_b.release()
print(f'用户{self.name}释放锁b')
mutex_a.release()
print(f'用户{self}释放锁a')
def func2(self):
mutex_b.acquire()
print(f'用户{self.name}抢到锁b')
# IO操作
rime.sleep(1)
mutex_a.acquire()
print(f'用户{self.name}抢到锁a')
mutex_a.release()
print(f'用户{self.name}释放锁a')
mutex_b.release()
print(f'用户{self.name}释放锁b')
for line in range(10):
t = MyThread()
t.start()
注意:
锁不能乱用
递归锁(了解)
用于解决死锁问题。
RLock:比喻成万能钥匙,可以提供给多个人用,但是第一个使用的时候,会对该锁做一个引用计数.只有引用计数为0,才能真正释放让另一个去使用。
from threading import RLock, Thread, Lock
import time
mutex_a = mutex_b = Lock()
class MyThread(Thread):
# 线程执行任务
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
# print(f'用户{current_thread().name}抢到锁a')
print(f'用户{self.name}抢到锁a')
mutex_b.acquire()
print(f'用户{self.name}抢到锁b')
mutex_b.release()
print(f'用户{self.name}释放锁b')
mutex_a.release()
print(f'用户{self.name}释放锁a')
def func2(self):
mutex_b.acquire()
print(f'用户{self.name}抢到锁b')
# IO操作
time.sleep(1)
mutex_a.acquire()
print(f'用户{self.name}抢到锁a')
mutex_a.release()
print(f'用户{self.name}释放锁a')
mutex_b.release()
print(f'用户{self.name}释放锁b')
for line in range(10):
t = MyThread()
t.start()
信号量(了解):
互斥锁:
比喻成一个家用马桶. 同一时间只能让一个人去使用
信号量:
比喻成公厕多个马桶. 同一时间可以让多个人去使用
from threading import Semaphore, Lock
from threading import current_thread
from threading import Thread
import time
sm = Semaphore(5) # 5个马桶
mutex = Lock() # 5个马桶
def task():
sm.acquire()
print(f'{current_thread().name}执行任务')
time.sleep(1)
sm.release()
# mutex.release()
for line in range(20):
t = Thread(target=task)
t.start()
线程队列
线程Q(了解级别1): 线程队列 面试会问: FIFO
FIFO队列:
先进先出
LIFO队列:
后进先出
优先级队列:
根据参数内,数字的大小进行分级,数字值越小,优先级越高
import queue
# 普通的线程队列: 先进先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 1
# LIFO队列: 后进先出
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 3
基于TCP协议套接字,服务端实现接收客户端的连接并发。
客户端
# client 客户端
import socket
client = socket.socket()
client.connect(
('127.0.0.1', 8866)
)
while True:
cmd = input('hello>>: ').strip()
if not cmd:
continue
if cmd =='q':
break
client.send(cmd.encode('utf-8'))
data = client.recv(1024)
print(data)
client.close()
服务端
# server服务端
import socket
from threading import Thread
import threading
server = socket.socket()
server.bind(
('127.0.0.1', 8866)
)
server.listen(2)
def action(conn):
while True:
data = conn.recv(1024)
print(data)
conn.send(data.upper())
if __name__ == '__main__':
while True:
conn, addr = server.accept()
p = threading.Thread(target=action, args=(conn,))
p.start()
server.close()
3.协程
进程:
资源单位
线程:
执行单位
协程:
在单线程下实现并发。
在IO密集型的情况下,使用协程能提高最高效率。
注意:
协程不是操作系统资源,他是程序取的名字,为让单线程能实现并发。
协程的目的:
操作系统:躲到技术,切换+保存状态
躲到技术,切换+保存状态
- 手动实现 "遇到IO切换 + 保存状态" 去欺骗操作系统,让操作系统误以为没有IO操作,将CPU的执行权限给你。
1.遇到 IO
2.CPU执行时间长
协程意义:
通过手动模拟操作系统‘多道技术’,实现 切换+保存。
1.手动实现 遇到 IO却换,欺骗操作系统误以没有 IO操作。
单线程 遇到 IO ,切换 + 保存状态。
单线程 计算密集型。来回切换 + 保存状态,反而效果更低
优点:
在 IO密集型的情况下,会提高效率。
缺点:
若在计算密集型的情况下,来回切换,反而效率更低。
如何实现协程:切换 + 保存状态。
基于 yield 并发执行(验证计算密集型的情况下,反而效率更低)
并发:切换
# 串行执行(串行比协程并发性更高)
import time
def func1():
for i in range(10000000):
i+1
def func2():
for i in range(10000000):
i+1
start = time.time()
func1()
func2()
stop = time.time()
print(stop-start) #1.0149040222167969
# 验证计算密集型的情况下效果更低 # 1.2908563613891602 还比如串行
# 基于yield并发执行
import time
def func1():
while True:
10000000+1
yield
def func2():
# g 生成器对象
g = func1()
for i in range(10000000):
# time.sleep(100)# 模拟 IO,yield 并不会捕捉到并自动切换。
i+1
next(g) #每次执行next相当与切换到func1下面。
start = time.time()
func2()
stop = time.time()
print(stop-start) # 1.2908563613891602
# 两个都是计算密集型,但是通过手动实现 切换+保存执行效率会低一些(用的秒长一些)。
gevent
是一个第三方模块,可以帮你监听 IO操作,并切换。
需要下载:pip3 install gevent
使用gevent目的:
为了实现单行线程下,实现遇到 IO, 保存状态 +切换