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()