Python併發編程以及系統常用模塊

Python併發編程以及系統常用模塊

全局解釋器鎖GIL

它是在實現Python解析器時所引入的一個概念
GIL是一把全局排他鎖,同一時刻只有一個線程在運行。
毫無疑問全局鎖的存在會對多線程的效率有不小影響。甚至就幾乎等於Python是個單線程的程序。
multiprocessing庫的出現很大程度上是爲了彌補thread庫因爲GIL而低效的缺陷。它完整的複製了一套thread所提供的接口方便遷移。唯一的不同就是
它使用了多進程而不是多線程。每個進程有自己獨立的GIL,因此也不會出現進程之間的GIL爭搶。

順序執行單線程與同時執行兩個併發線程

from threading improt Thread
import time

def my_counter():
    i = 0
    for _ in range(1000000):
        i = i + 1
    return True

依次創建線程後 join 來阻塞進程直到線程執行完畢, 因此是順序執行單線程

def main():
    thread_array = {}
    start_time = time.time()
    for tid in range(2):
        t = Thread(target = my_counter)
        t.start()
        t.join()
    end_time = time.time()
    print("Total time:{}".format(end_time - start_time))

if __name__ == '__main__':
    main()

先創建完所有線程start後,最後再依次輪詢所有線程join() 因此是併發執行

def main():
    thread_array = {}
    start_time = time.time()
    for tid in range(2):
        t = Thread(target = my_counter)
        t.start()
        thread_array[tid] = t
    for i in range(2):
        thread_array[i].join()
    end_time = time.time()
    print("Total time:{}".format(end_time - start_time))

multiprocessing模塊

是跨平臺版本的多進程模塊,提供了一個Process類來代表一個進程對象,下面是示例:

from multiprocessing import Process
import time
def f(n):
    time.sleep(l)
    print(n*n)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target = f, args = [i,])
        p.start()

這個程序如果用單進程寫則需要執行10s以上的時間,而用多進程則啓動10個進程並行執行,只需要1s多的時間

進程間通信Queue

from multiprocessing import Process,Queue
import time

def write(q):
    for i in ['A','B','C','D','E']:
        print('Put %s to queue' % i)
        q.put(i)
        time.sleep(0.5)
    
def read(q):
    while True:
        v = q.get(True)
        print('get %s from queue' % v)

if __name__ == '__main__':
    q = Queue()
    pw = Process(target = write, args=(q,))
    pr = Process(target = read, args=(q,))
    pw.start()
    pr.start()
    pr.join()
    pr.terminate()

進程池Pool

用於批量創建子進程,可以靈活控制子進程的數量,之前我們創建進程使用Process創建一個,這個Pool批量創建多個,更簡單了

from multiprocessing import Pool
import time

def f(x):
    print(x*x)
    time.sleep(2)
    return x*x

if __name__ == '__main__':
    '''定義啓動的進程數量'''
    pool = Pool(processes=5)
    res_list = []

    for i in range(10):
        '''以一部並行的方式啓動進程,如果要同步等待的方式,可以在每次啓動進程之後調用res.get()方法,也可以使用Pool.apply '''
        res = pool.apply_async(f, [i,])
        print('------------:',i)
        res_list.append(res)
    
    pool.close()
    pool.join()
    for r in res_list:
        print("result", r.get(timeout=5))  

進程與線程對比

在一般情況下,多線程是可以共享同一個進程的內存資源,無需額外操作即可直接交換多個線程間數據,這是使用多線程的方便之處,當然也正由於數據共享,引發了數據同步問題。
但進程之間是數據隔離的,內存資源相互獨立,不能直接共享。

由於進程之間不共享數據,對於每個進程res_list[]都是空列表,所以在併發執行的時候,執行結果是

[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
------------------------threading---------------------------
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

輸出結果各自獨立的res_list,也印證了多進程數據獨立不共享。


from multiprocessing import Process
import threading
import time
lock = threading.Lock()

def run(info_list, n):
    lock.acquire()   # lock.acquire()會鎖住臨界區,教程中說是會Lock上進程或線程使用到的所有資源,包括內存數據。我覺得不對,應該是隻Lock住 lock.acquire() {block}  lock.release()包括的代碼塊
    info_list.append(n)
    lock.release()
    print('%s\n' % info_list)

if __name__ == '__main__':
    info = []
    for i in range(10):
        # target爲子進程執行的函數,args爲需要給函數傳遞的參數
        p = Process(target=run, args=[info, i]) # args對應run的參數列表
        p.start()
        p.join()
    time.sleep(1) # 這裏是爲了輸出整齊讓主進程的執行等一下子進程
    print('---------------threading----------------')
    for i in range(10):
        p = threading.Thread(target=run, args=[info, i])
        p.start()
        p.join()   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章