一、GIL锁
-
什么是GIL:全局解释器锁,是加在解释器上的互斥锁
-
为什么需要GIL:由于CPython的内存管理是非线程安全,于是CPython就给解释器加了个锁,解决了安全问题 ,但是降低了效率。另外,虽然有解决方案,但是由于牵涉太多,一但修改则很多以前基于GIL的程序都需要修改,所以变成了历史遗留问题
-
GIL带来的问题:即使在多核处理器下也无法真正的并行
总结:
- 在单核情况下,无论是IO密集还是计算密集GIL都不会产生影响
- 如果是多核下,IO密集型会受到GIL的影响,但是很明显IO速度远比计算速度慢
- IO密集型用多线程,因为多线程开销小,节省资源,对于计算密集型应该使用多进程,因为在CPython解释器中多线程是无法并行的
二、多线程与多进程效率对比
计算密集型多线程耗时:
from threading import Thread
from multiprocessing import Process
import time
a = 1
def task():
global a
for i in range(10000000):
a += 1
a * 10 / 2 - 3
s = time.time()
t1 = Thread(target=task)
t2 = Thread(target=task)
t3 = Thread(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(time.time() - s) #耗时为8.482369184494019
计算密集型多进程耗时:
from threading import Thread
from multiprocessing import Process
import time
a = 1
def task():
global a
for i in range(10000000):
a += 1
a * 10 / 2 - 3
s = time.time()
if __name__ == '__main__':
t1 = Process(target=task)
t2 = Process(target=task)
t3 = Process(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(time.time() - s) #耗时为6.604919672012329
IOIO密集型多线程耗时:
from threading import Thread
from multiprocessing import Process
import time
def task():
# for i in range(10):
with open(r"D:\软件包\镜像\CentOS-7-x86_64-DVD-1611.iso",mode="rb")as f:
while True:
data = f.read(1024)
if not data:
break
s = time.time()
t1 = Thread(target=task)
t2 = Thread(target=task)
t3 = Thread(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(time.time() - s) #耗时为18.977698802947998
IO密集型多进程耗时:
from threading import Thread
from multiprocessing import Process
import time
def task():
# for i in range(10):
with open(r"D:\软件包\镜像\CentOS-7-x86_64-DVD-1611.iso",mode="rb")as f:
while True:
data = f.read(1024)
if not data:
break
s = time.time()
if __name__ == '__main__':
t1 = Process(target=task)
t2 = Process(target=task)
t3 = Process(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(time.time() - s) #耗时为11.617670774459839
三、GIL与自定义线程锁的区别
from threading import Thread,Lock
import time
lock = Lock()
a = 0
def task():
global a
lock.acquire()
temp = a
time.sleep(0.01)
a = temp + 1
lock.release()
ts = []
for i in range(10):
t1 = Thread(target=task)
t1.start()
ts.append(t1)
for i in ts:
i.join()
print(a) # 10
总结:GIL使用用于保护解释器相关的数据,解释器也是一段程序,肯定有其定义各种数据, GIL并不能保证你自己定义的数据的安全,所以一旦你的程序中出现了多线程共享数据时就需要自己加锁
扫描二维码关注公众号,回复:
4940456 查看本文章
四、进程池和线程池的使用
线程池使用:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import active_count,current_thread
import os,time
#创建线程池,指定最大线程数为3,如果不指定,默认为CPU核心数 * 5
pool = ThreadPoolExecutor(3) #不会立即开启子线程
def task():
print("%s running.." % current_thread().name)
time.sleep(1)
#提交任务到线程池
for i in range(10):
pool.submit(task)
进程池使用:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import active_count,current_thread
import os,time
#创建进程池,最大进程数为3,默认为cpu个数
pool = ProcessPoolExecutor(3)
def task():
print("%s running.." % os.getpid())
time.sleep(1)
if __name__ == '__main__':
#提交任务到进程池
for i in range(10):
#第一次提交任务时会创建进程,后续再提交任务直接交给已经存在的进程来完成,如果没有空闲进程就等待
pool.submit(task)
总结:与信号量的区别,信号量也是一种锁,适用于保证同一时间能有多少个进程或线程访问。而线程/进程池,没有对数据访问进行限制仅仅是控制数量
五、同步与异步
-
阻塞:程序遇到了IO操作,无法继续执行代码,叫做阻塞
-
非阻塞:程序没有遇到IO操作,正常执行中,就叫非阻塞
-
同步:发起任务后必须等待任务结束,拿到一个结果才能继续运行(调用/执行/任务/提交)
-
异步:发起任务后不需要关心任务的执行过程,可以继续往下运行
异步效率高于同步,但是并不是所有任务都可以异步执行,判断一个任务是否可以异步的条件是任务发起方是否立即需要执行结果
同步不等于阻塞,异步不等于非阻塞,当使用异步方式发起任务时,任务中可能包含io操作,异步也可能阻塞。同步提交任务也会卡主程序,但是不等同阻塞,因为任务中可能在做一对计算任务,CPU没走
使用线程池来执行异步任务:
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor()
def task(i):
time.sleep(1)
print('sub thread run...')
i += 100
return i
fs = []
for i in range(10):
#submit就是一异步的方式提交任务
f = pool.submit(task,i)
fs.append(f)
#是一个阻塞函数,会等到池子中所有任务完成后继续执行
pool.shutdown(wait=True)
for i in fs:
#result是阻塞的,会等到这任务执行完成才继续执行,会异步变成同步
print(i.result())
print('over')
练习:
多线程tcp服务端:
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
import socket
server = socket.socket()
server.bind(("127.0.0.1",8989))
server.listen()
pool = ThreadPoolExecutor(3)
def task(client):
while True:
try:
data = client.recv(1024)
if not data:
client.close()
break
client.send(data.upper())
except Exception:
client.close()
break
while True:
client,addr = server.accept()
pool.submit(task,client)
多线程tcp客户端:
from threading import Thread
import socket
c = socket.socket()
c.connect(("127.0.0.1",8989))
def send_msg():
while True:
msg = input(">>>:")
if not msg:
continue
c.send(msg.encode("utf-8"))
send_t = Thread(target=send_msg)
send_t.start()
while True:
try:
data = c.recv(1024)
print(data.decode("utf-8"))
except:
c.close()
break