1、python的多线程
多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有一个线程被执行。
多线程的特点:
线程比进程更轻量级,创建一个线程要比创建一个进程快10-100倍。
线程共享全局变量。
由于GIL的原因,当一个线程遇到IO操作时,会切换到另一个线程,所以线程适合IO密集型操作。
在多核cpu系统中,最大限度的利用多核,可以开启多个线程,开销比进程小的多,但是这并不适合python。
多线程互斥锁:
因为线程共享全局变量,所以需要互斥锁去限制线程对全局变量的更改。
假设,当一个线程在执行到获取全局变量的时候,这个后GIL切换到另一个线程执行,这个时候新的线程为全局变量+1后切换回之前的线程,之前的线程中的全局变量还是+1前的值,所以需要互斥锁。
为什么有了GIL锁还要互斥锁呢?
GIL锁只是控制同一时刻下只有一个线程被执行,这并不能控制同一时刻只有一个线程去获取并更改全局变量,所以需要使用互斥锁。
多线程的实现:
#导入threading模块
importthreading#定义全局变量
i=0#定义互斥锁
mutex =threading.Lock()defa():#申明全局变量i
globalifor j in range(2000000):#获取互斥锁
mutex.acquire()
i+=1
#释放互斥锁
mutex.release()defb():globalifor j in range(2000000):
mutex.acquire()
i+=1mutex.release()#创建线程
t1 = threading.Thread(target=a)
t2= threading.Thread(target=b)#开启线程
t1.start()
t2.start()#等待所有线程结束
t1.join()
t2.join()print(i)
2、python中的多进程
python的多线程不能利用多核的优势,如果想要充分的利用多核cpu的资源,python中大部分情况需要使用多进程。
python多进程的特点:
进程间不共享全局变量,进程修改的数据仅限于该进程内。
进程创建和销毁的开销比较大。
相对于线程,进程更适合与计算密集型操作。
能充分利用多核的优势。
进程间通信:
既然进程间中不公共享全局变量,那么多进程间怎么进行通信呢?可以使用multiprocessing中的Queue模块,当然也可以使用socket、管道、共享内存等方式。
多进程的实现:
#导入multiprocessin模块
importmultiprocessing#创建队列
queue =multiprocessing.Queue()#定义全局变量
a =0#定义函数
defwork1(num):#获取队列中的数据,如果没有数据,将堵塞
a =queue.get()#将队列中的数据+2000000次num
for i in range(2000000):
a+=num#将数据存放在队列中
queue.put(a)#打印最终结果
print("work1",a)#定义函数
defwork2():#申明全局变量a
globala#将a+2000000次1
for i in range(2000000):
a+=1
#打印最总结果
print("work2",a)#将a存放在队列中
queue.put(a)#创建进程
p1 = multiprocessing.Process(target=work1, args=(2,))
p2= multiprocessing.Process(target=work2)#启动进程
p1.start()
p2.start()#等待进程结束
p1.join()
p2.join()#获取队列中的数据
a =queue.get()#打印a
print(a)
进程池的实现
进程池能减少重复创建和销毁进程的开销问题
#导入需要的模块
importmultiprocessingimporttimeimportrandom#定义函数
defwork(num):print("num=",num)
time.sleep(random.randint(0,2))#创建进程池,设置进程的数量
pool = multiprocessing.Pool(3)for i in range(10):#开启进程
pool.apply_async(work, args=(i,))#设置等待时间,等待所有进程结束
time.sleep(20)
3、python中的协程
在linux中线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程。
对比进程和协程:
进程是内核调度,而协程是在用户态调度,所以说进程的上下文在内核态保存恢复,而协程是在用户态保存恢复的,所以协程的开销比进程低。
进程会被抢占,而协程不会,也就是说协程如果不主动让出cpu,那么其他的协程就没有执行的机会。
进程所需要的内存比协程大得多
对比线程和协程:
线程的上下文切换成本相对于协程来说比较高。
线程的切换由操作系统来控制,而协程的切换由我们自己控制。
yield实现协程:
#定义两个函数
defwork1():whileTrue:print("work1")#当程序运行到yield就会暂停,等待下次的next调用,然后继续执行
yield
defwork2():whileTrue:print("work2")yieldw1=work1()
w2=work2()whileTrue:#使用next函数启动
next(w1)
next(w2)
greenlet实现协程:
greenlet安装:
sudo pip3 install greenlet
code:
#导入greenlet模块
from greenlet importgreenletdefwork1():for i in range(10):print("work1")#打印过后跳转至协程g2继续执行
g2.switch()defwork2():for i in range(10):print("work2")#打印后跳转至协程g1继续执行
g1.switch()#创建协程g1
g1 =greenlet(work1)#创建协程g2
g2 =greenlet(work2)#跳转至协程g1
g1.switch()
gevent实现协程:
gevent是基于greenlet的并发网络库,每当有一个协程堵塞的时,程序将自动调度。
monkey-patching:
一般称为猴子补丁,这个补丁能直接修改标准库里面大部分的阻塞式系统调用。但是如果在复杂的生产环境中使用了这些标准库,可能就会因为打了补丁而出现奇怪的问题。
gevent安装:
sudo pip3 install gevent
code:
#导入所需要的模块
importgeventimporttimefrom gevent importmonkey#猴子补丁,monkey.patch_all()方法将所有的标准库都替换掉#使用猴子补丁褒贬不一,但是官网上还是建议使用patch_all(),而且在程序的第一行就执行
monkey.patch_all()deff(n):for i inrange(n):print(i)#设置延时
time.sleep(0.5)#如果没有导入monkey模块的话,需要使用gevent.sleep()
#gevent.sleep(0.5)
#----------------写法一--------------------#创建greenlet协程对象#g1 = gevent.spawn(f,5)#g2 = gevent.spawn(f,5)#g3 = gevent.spawn(f,5)#等待所有greenlet携程结束后退出#g1.join()#g2.join()#g3.join()
#----------------写法二--------------------
gevent.joinall([gevent.spawn(f,5), gevent.spawn(f,5), gevent.spawn(f,5)])