進程與線程
* 對於操作系統來說,一個任務就是一個進程(Process),比如打開一個瀏覽器就是啓動一個瀏覽器進程,打開一個記事本就啓動了一個記事本進程,打開兩個記事本就啓動了兩個記事本進程,打開一個Word就啓動了一個Word進程。*
很好理解,當你啓動了QQ和微信那麼對於操作系統來說就是有兩個進程在執行;
有些進程還不止同時幹一件事,比如Word,它可以同時進行打字、拼寫檢查、打印等事情。在一個進程內部,要同時幹多件事,就需要同時運行多個“子任務”,我們把進程內的這些“子任務”稱爲線程(Thread)。
在QQ和微信中有多少小功能在運行(打字,小程序,視頻等) 那麼這些便可以稱之爲這兩個進程的線程;
由於每個進程至少要做一件事情 故 每個進程其必有一個線程。
理解時可以聯想下書的目錄與樹的內容。
多任務實現
1.多進程模式; 每個進程對應一個線程 啓動多個 進程
2.多線程模式; 每個進程對應多個線程 啓動一個進程 即啓動多個線程
3.多進程+多線程模式。 每個進程對應多個線程 啓動多個進程
多線程
多線程就是允許一個進程內存在多個控制權,以便讓多個函數同時處於激活狀態,從而讓多個函數的操作同時運行;多線程相當於一個併發系統。併發系統一般同時執行多個任務。如果多個任務可以同時共享資源,比如說同時寫入某個變量的時候,就必須處理同步上的問題;
線程的創建用threading模塊。
多線程沒有想象中的那麼費勁,threading.Thread() 創建線程 ;
下面看一個例子:
def sleep_3():
time.sleep(3)
def sleep_5():
time.sleep(5)
if __name__ == '__main__':
start_time = time.time()
print('start sleep 3')
sleep_3()
print('start sleep 5')
sleep_5()
end_time = time.time()
print(str(end_time - start_time) + ' s')
執行結果
多線程改造:
if __name__ == '__main__':
'''利用多線程改造'''
start_time_1 = time.time()
print('threading start sleep 3')
thread_1 = threading.Thread(target=sleep_3) # 實例化一個線程對象
thread_1.start() # 使線程執行這個函數
print('threading start sleep 5')
thread_2 = threading.Thread(target=sleep_5)
thread_2.start()
thread_1.join()
thread_2.join()
end_time_1 = time.time()
print(str(end_time_1 - start_time_1) + ' s')
執行結果:
在併發狀況下,指令執行的先後順序是根據內核決定。在同一個線程內部,執行順序必然是有先後的執行;但是在不同線程間的指令併發時,就需要考慮多線程同步問題。對多線程程序來說,同步(synchronization)是指在一定的時間內只允許某一個線程訪問某個資源;在這段時間內,不允許其他的線程操作該資源。
解決多線程同步方案
給判斷是否有餘票和賣票,加上互斥鎖,這樣就不會造成一個線程剛判斷沒有餘票,而另外一個線程就執行賣票操作。
def booth(tid):
global i
# 默認互斥鎖是 open狀態
global lock
while True:
# 獲取鎖 並鎖定該鎖
lock.acquire()
if i != 0:
i = i - 1
print("窗口:", tid, ",剩餘票數:", i)
time.sleep(1)
else:
print("Thread_id", tid, "No more tickets")
os._exit(0)
# 線程執行完畢 互斥鎖 釋放
lock.release()
time.sleep(1)
if __name__ == '__main__':
i = 19
# 創建鎖
lock = threading.Lock()
for k in range(1,10):
# 這裏參數傳遞的是一個元組 單數字元素元組定義加 逗號
new_thread = threading.Thread(target=booth, args=(k,))
new_thread.start()
注:由於GIL(全局解釋鎖)的問題,python多線程並不能充分利用多核處理器
多進程
對於進程概念,是指正在進行的一個過程或者說一個任務,而負責執行任務的則是CPU,進程本身是一個抽象的概念。進程的創建:用戶創建出來的所有進程都是由操作系統負責的,因此無論是哪一種創建進程的方式,實際上都是調用操作系統的接口創建的,進程的切換都是由操作系統控制的。
無論哪一種創建進程的方式,新進程的創建都是由一個已經存在的進程執行了一個用於創建進程的系統調用而創建的。
進程的創建用multiprocessing模塊。
'''進程的創建用multiprocessing模塊'''
def run_proc(name):
# 輸出傳入姓名並打印當前進程號
print('Run child process %s (%s)'%(name,os.getpid()))
print('Parent process1 %s.' % os.getpid())
if __name__ == '__main__':
print('Parent process2 %s.' % os.getpid())
# 創建進程run_proc,傳參
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
print('Parent process3 %s.' % os.getpid())
從結果來看 process1 與 process2 process3 共用同一進程 新進程的進程號爲30957。
創建進程的類:Process([group [, target [, name [, args [, kwargs]]]]])
target表示調用對象。
args表示調用對象的位置參數元組。
kwargs表示調用對象的字典。
name爲別名。group實質上不使用。
進程的一般方法:is_alive()、join([timeout])、run()、start()、terminate()、name()
if __name__ == '__main__':
a = multiprocessing.Process(target=run_proc,args=('sulong',))
a.start()
print('p.pid',a.pid)
print('p.name',a.name)
print('p.is_alive',a.is_alive())
# 終止當前進程
print('p.terminate', a.terminate())
下面來扯扯多進程:
def task1(msg):
print('task1:hello,%s' %(msg))
time.sleep(1)
def task2(msg):
print('task2:hello,%s' %(msg))
time.sleep(1)
def task3(msg):
print('task3:hello,%s' %(msg))
time.sleep(1)
if __name__ == '__main__':
p1 = Process(target=task1, args=('one',))
p2 = Process(target=task2, args=('two',))
p3 = Process(target=task3, args=('three',))
start = time.time()
p1.start()
p2.start()
p3.start()
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name: " + p.name + "\tp.id: " + str(p.pid))
p1.join()
p2.join()
p3.join()
end = time.time()
print('3 processes take %s seconds' % (end - start))
結果:
The number of CPU is:4
child p.name: Process-3 p.id: 31105
child p.name: Process-1 p.id: 31103
child p.name: Process-2 p.id: 31104
task1:hello,one
task2:hello,two
task3:hello,three
3 processes take 1.0111210346221924 seconds
三個進程執行花費約1s,說明程序是併發執行的。
併發與並行又該如何理解?
你吃飯吃到一半,電話來了,你一直到吃完了以後纔去接,這就說明你不支持併發也不支持並行。
你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支持併發。
你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持並行。
併發的關鍵是你有處理多個任務的能力,不一定要同時。
並行的關鍵是你有同時處理多個任務的能力。
所以它們最關鍵的點就是:是否是『同時』;Python 中沒有真正的並行,只有併發
無論你的機器有多少個CPU, 同一時間只有一個Python解析器執行
來自知乎的解答
如果要啓動大量的子進程,可以用進程池的方式批量創建子進程:
def long_time_task(name):
# 啓動一個進程
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
#睡隨機時間
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconde' % (name, (end - start)))
if __name__ == '__main__':
print('Parent process %s.' % os.getpid())
# 定義一個進程池 個數爲5
p = Pool(5)
# 執行 8個進程
# 根據輸出結果看出 在進程池中只有5個進程位置 誰先執行完畢 則下一個進程 進入該進程執行
for i in range(0, 8):
# apply_asyncl
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
# 必須對Pool先調用close()方法才能join
p.join()
print('All subprocesses done.')
執行結果:
Parent process 31270.
Waiting for all subprocesses done...
Run task 0 (31276)...
Run task 1 (31277)...
Run task 2 (31278)...
Run task 3 (31279)...
Run task 4 (31280)...
Task 3 runs 0.01 seconde
Run task 5 (31279)...
Task 0 runs 0.16 seconde
Run task 6 (31276)...
Task 6 runs 0.31 seconde
Run task 7 (31276)...
Task 2 runs 0.65 seconde
Task 4 runs 1.26 seconde
Task 5 runs 1.43 seconde
Task 1 runs 1.88 seconde
Task 7 runs 2.11 seconde
All subprocesses done.
對Pool對象調用join()方法會等待所有子進程執行完畢,調用join()之前必須先調用close(),調用close()之後就不能繼續添加新的Process了。
當多個進程需要訪問共享資源的時候,Lock可以用來避免訪問的衝突
# 多進程共享資源lock
def task4(lock,f):
with lock:
f = open(f,'w+')
f.write('hello')
time.sleep(1)
f.close()
def task5(lock,f):
lock.acquire()
try:
f = open(f,'a+')
time.sleep(1)
f.write('world!')
except Exception as e :
print(e)
finally:
f.close()
lock.release()
運行結果:
>>time cost :2.0073862075805664 seconds
>>helloworld!
因爲要訪問共享文件,先獲得鎖的進程會阻塞後面的進程,因此程序運行耗時約2s。
python通過多進程實現多並行,充分利用多處理器,彌補了語言層面不支持多並行的缺點
協程
根據維基百科給出的定義,“協程 是爲非搶佔式多任務產生子程序的計算機程序組件,協程允許不同入口點在不同位置暫停或開始執行程序”。從技術的角度來說,“協程就是你可以暫停執行的函數”
協程可以理解爲生成器
其他博主講解的生成器大家可以看一下,在這就不做詳解了。
借鑑於:https://www.cnblogs.com/tyomcat/p/5486827.html,
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000