Python—程序、執行緒、協程
一、執行緒
執行緒是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務
方法:
start 執行緒準備就緒,等待CPU排程
setName 設定執行緒名稱
getName 獲取執行緒名稱
setDaemon 把一個主程序設定為Daemon執行緒後,主執行緒執行過程中,後臺執行緒也在進行,主執行緒執行完畢後,後臺執行緒不論有沒執行完成,都會停止
join 逐個執行每個執行緒,執行完畢後繼續往下執行,該方法使得多執行緒變得無意義
run 執行緒被cpu排程後自動執行執行緒物件的run方法
threading模組
執行緒的兩種呼叫方式:
1.直接呼叫(常用)
import threading import time '''直接呼叫''' def hello(name): print("Hello %s"%name) time.sleep(3) if __name__ == "__main__": t1=threading.Thread(target=hello,args=("zhangsan",)) #生成執行緒例項 t2=threading.Thread(target=hello,args=("lisi",)) t1.setName("aaa") #設定執行緒名 t1.start() #啟動執行緒 t2.start() t2.join() #join 等待t2先執行完 print("Hello") print(t1.getName()) #獲取執行緒名
2.繼承式呼叫
'''繼承式呼叫''' import threading import time class MyThread(threading.Thread): def __init__(self,name): threading.Thread.__init__(self) self.name = name def 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 time num = 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 time num = 100 #設定一個共享變數 lock=threading.Lock() #生成全域性鎖 def show(): global num #在函式內操作函式外變數,需設定為全域性變數 time.sleep(1) lock.acquire() #修改前加鎖 num -= 1 lock.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 num num +=1 lock.release() return num def run2(): lock.acquire() #小鎖 global num2 num2+=1 lock.release() return num2 def run3(): lock.acquire() #大鎖 res = run1() res2 = run2() lock.release() print(res,res2) if __name__ == '__main__': num,num2 = 0,0 lock = threading.RLock() #生成Rlock for 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, Pipe def 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'] = 1 dic['2'] = 2 dic['3'] = 3 list.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 time def Foo(i): time.sleep(1) return i+100 def 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 time def 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