1. 程式人生 > >Python—程序、執行緒、協程

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