Python—进程、线程、协程
一、線程
線程是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務
方法:
start ? ? ? ? ? ?線程準備就緒,等待CPU調度
setName ? ? ?設置線程名稱
getName ? ? ?獲取線程名稱
setDaemon ? 把一個主進程設置為Daemon線程后,主線程執行過程中,后臺線程也在進行,主線程執行完畢后,后臺線程不論有沒執行完成,都會停止
join ? ? ? ? ? ? ?逐個執行每個線程,執行完畢后繼續往下執行,該方法使得多線程變得無意義
run ? ? ? ? ? ? ?線程被cpu調度后自動執行線程對象的run方法
?
threading模塊
線程的兩種調用方式:
1.直接調用(常用)
2.繼承式調用
'''繼承式調用''' import threading import time class MyThread(threading.Thread):def __init__(self,name):threading.Thread.__init__(self)self.name = namedef run(self):print("Hello %s"%self.name)time.sleep(3)if __name__ == "__main__":t1=MyThread("zhangsan")t2=MyThread("lisi")t1.start()t2.start()?
?
setDaemon線程
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import time import threading def run(n):print('Hello..[%s]\n' % n)time.sleep(2)def main():for i in range(5):t = threading.Thread(target=run,args=[i,])t.start()t.join(1)m = threading.Thread(target=main,args=[]) m.setDaemon(True) #將主線程設置Daemon設置為True后,主線程執行完成時,其它子線程會同時退出,不管是否執行完任務 m.start() print("--- done----")?
?
線程鎖Lock
一個進程下可以啟動多個線程,多個線程共享父進程的內存空間,每個線程可以訪問同一份數據,所以當多個線程同時要修改同一份數據時,就會出現錯誤
例如:
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import timenum = 100 #設置一個共享變量 def show():global num #在函數內操作函數外變量,需設置為全局變量time.sleep(1)num -= 1 list=[] for i in range(100):t = threading.Thread(target=show)t.start()list.append(t)for t in list:t.join() print(num)?
上面的例子在正常執行完成后的num的結果應該是0,但實際上每次的執行結果都不太一樣,因為當多個線程同時要修改同一份數據時,就會出現一些錯誤(只有
在python2.x運行才會出現錯誤,python3.x中不會),所以每個線程在要修改公共數據時,為了避免自己在還沒改完的時候別人也來修改此數據,可以加上線程鎖
來確保每次修改數據時只有一個線程在操作。
加鎖代碼
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import timenum = 100 #設置一個共享變量 lock=threading.Lock() #生成全局鎖 def show():global num #在函數內操作函數外變量,需設置為全局變量time.sleep(1)lock.acquire() #修改前加鎖num -= 1lock.release() #修改后解鎖 list=[] for i in range(100):t = threading.Thread(target=show)t.start()list.append(t)for t in list:t.join()print(num)?
遞歸鎖RLock
就是在一個大鎖中再包含子鎖
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading #遞歸鎖 def run1():lock.acquire() #小鎖global numnum +=1lock.release()return num def run2():lock.acquire() #小鎖global num2num2+=1lock.release()return num2 def run3():lock.acquire() #大鎖res = run1()res2 = run2()lock.release()print(res,res2)if __name__ == '__main__':num,num2 = 0,0lock = threading.RLock() #生成Rlockfor i in range(10):t = threading.Thread(target=run3)t.start()while threading.active_count() != 1:#如果不等于1,說明子線程還沒執行完畢pass #打印進程數 else:print(num,num2)?
Semaphore
同時允許一定數量的線程更改數據
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time def run(n):semaphore.acquire()time.sleep(1)print("run the thread: %s" %n)semaphore.release()if __name__ == '__main__':semaphore = threading.BoundedSemaphore(3) #設置最多允許3個線程同時運行for i in range(20):t = threading.Thread(target=run,args=(i,))t.start() while threading.active_count() != 1:pass else:print('----done---')?
event
實現兩個或多個線程間的交互,提供了三個方法?set、wait、clear,默認碰到event.wait 方法時就會阻塞。
event.set(),設定后遇到wait不阻塞
event.clear(),設定后遇到wait后阻塞
event.isSet(),判斷有沒有被設定
?
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading def start():print("---start---1")event.wait() #阻塞print("---start---2")if __name__ == "__main__":event = threading.Event()t = threading.Thread(target=start)t.start()result=input(">>:")if result == "set":event.set() #設定set,wait不阻塞?
?
二、進程
multiprocessing模塊
進程調用
?
#!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process import time def start(name):time.sleep(1)print('hello', name)if __name__ == '__main__':p = Process(target=start, args=('zhangsan',))p1 = Process(target=start, args=('lisi',))p.start()p1.start()p.join()?
進程間通訊
每個進程都擁有自己的內存空間,因此不同進程間內存是不共享的,要想實現兩個進程間的數據交換,有幾種方法
Queue(隊列)
?
#!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process, Queue def start(q):q.put( 'hello')if __name__ == '__main__':q = Queue()p = Process(target=start, args=(q,))p.start()print(q.get())p.join()?
Pipe(管道,不常用)
把管道的兩頭分別賦給兩個進程,實現兩個進程的互相通信
?
#!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process, Pipedef start(conn):conn.send('hello')#發送print(conn.recv())#接收conn.close()if __name__ == '__main__':parent_conn, child_conn = Pipe() #生成一個管道p = Process(target=start, args=(child_conn,))p.start()print(parent_conn.recv())#接收parent_conn.send("11111")#發送p.join()?
Manager(實現了進程間真正的數據共享)
?
#!/usr/bin/env python from multiprocessing import Process, Manager def f(dic, list,i):dic['1'] = 1dic['2'] = 2dic['3'] = 3list.append(i)if __name__ == '__main__':manager = Manager()dic = manager.dict()#通過manager生成一個字典list = manager.list(range(5))#通過manager生成一個列表p_list = []for i in range(10):p = Process(target=f, args=(dic, list,i))p.start()p_list.append(p)for res in p_list:res.join()print(dic)print(list) #執行結果 ''' {'2': 2, '3': 3, '1': 1} [0, 1, 2, 3, 4, 1, 9, 2, 5, 3, 7, 6, 0, 8, 4] '''?
?
進程池
進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,如果進程池序列中沒有可供使用的進程,那么程序就會等待,直到進程池中有可用進程為止。
進程池中有兩個方法:
1、apply(同步)
2、apply_async(異步)
?
#!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process,Pool import timedef Foo(i):time.sleep(1)return i+100def Bar(arg):print('number::',arg)if __name__ == "__main__":pool = Pool(3)#定義一個進程池,里面有3個進程for i in range(10):pool.apply_async(func=Foo, args=(i,),callback=Bar)#pool.apply(func=Foo, args=(i,))pool.close()#關閉進程池pool.join()#進程池中進程執行完畢后再關閉,(必須先close在join)?
callback是回調函數,就是在執行完Foo方法后會自動執行Bar函數,并且自動把Foo函數的返回值作為參數傳入Bar函數
?
三、協程
協程,又稱微線程,是一種用戶態的輕量級線程。協程能保留上一次調用時的狀態,每次過程重入時,就相當于進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置,當程序中存在大量不需要CPU的操作時(IO),適用于協程。
協程有極高的執行效率,因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷。
不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。
因為協程是一個線程執行,所以想要利用多核CPU,最簡單的方法是多進程+協程,這樣既充分利用多核,又充分發揮協程的高效率。
那符合什么條件就能稱之為協程:1、必須在只有一個單線程里實現并發 2、修改共享數據不需加鎖 3、用戶程序里自己保存多個控制流的上下文棧 4、一個協程遇到IO操作自動切換到其它協程
python中對于協程有兩個模塊,greenlet和gevent。
?
Greenlet(greenlet的執行順序需要我們手動控制)
?
#!/usr/bin/env python # -*- coding:utf-8 -*- from greenlet import greenlet def test1():print (11)gr2.switch() #手動切換print (22)gr2.switch()def test2():print (33)gr1.switch()print (44)gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()?
gevent(自動切換,由于切換是在IO操作時自動完成,所以gevent需要修改Python自帶的一些標準庫,這一過程在啟動時通過monkey patch完成)
?
from gevent import monkey; monkey.patch_all() import gevent import timedef foo():print('11')time.sleep(3)print('22')def bar():print('33')print('44')gevent.joinall([gevent.spawn(foo),gevent.spawn(bar), ])?
運行結果:(從結果可以看出,它們是并發執行的)
11 33 44 22?
參考:
http://www.cnblogs.com/wupeiqi/articles/5040827.html
http://www.cnblogs.com/alex3714/articles/5230609.html
總結
以上是生活随笔為你收集整理的Python—进程、线程、协程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: flask + celery实现定时任务
- 下一篇: Python协程(真才实学,想学的进来)