前言

程序是什麼?

程序就是一個程式在一個數據集上的一次動態執行過程。程序一般由程式、資料集、程序控制塊三部分組成。我們編寫的程式用來描述程序要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;程序控制塊用來記錄程序的外部特徵,描述程序的執行變化過程,系統可以利用它來控制和管理程序,它是系統感知程序存在的唯一標誌。

執行緒是什麼?

執行緒也叫輕量級程序,它是一個基本的CPU執行單元,也是程式執行過程中的最小單元,由執行緒ID、程式計數器、暫存器集合和堆疊共同組成。執行緒的引入減小了程式併發執行時的開銷,提高了作業系統的併發效能。執行緒沒有自己的系統資源。

程序和執行緒的區別

程序是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。或者說程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位。

執行緒則是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。

程序和執行緒的關係:

(1)一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。

(2)資源分配給程序,同一程序的所有執行緒共享該程序的所有資源。

(3)CPU分給執行緒,即真正在CPU上執行的是執行緒。

並行和併發

並行處理(Parallel Processing)是計算機系統中能同時執行兩個或者更多個處理的一種計算方法。並行處理可同時工作於同一程式的不同方面,並行處理的主要目的是節省大型和複雜問題的解決時間。

併發處理(concurrency Processing)是指一個時間段中有幾個程式都處於已經啟動執行到執行完畢之間,而且這幾個程式都是在同一處理機(CPU)上執行,但任意時刻點上只有一個程式在處理機(CPU)上執行

同步和非同步

同步就是指一個程序在執行某個請求的時候,若該請求需要一段時間才能返回資訊,那麼這個程序將會一直等待下去,直到收到返回資訊才繼續執行下去;

非同步是指程序不需要一直等下去,而是繼續執行下面的操作,不管其他程序的狀態。當有訊息返回時系統會通知程序進行處理,這樣可以提高執行的效率。

舉個例子,打電話時就是同步通訊,發短息時就是非同步通訊。

單例執行

from random import randint
from time import time, sleep def download_task(filename):
print('開始下載%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main():
start = time()
download_task('Python入門.pdf')
download_task('av.avi')
end = time()
print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__':
main()

執行是順序執行,所以耗時是多個程序的時間總和

因為是單程序任務,所有任務都是排隊進行所以這樣執行效率非常的低。我們來新增多程序模式進行多程序同時執行,這樣一個程序執行時,另一個程序無需等待,執行時間將大大縮短。

多程序

from random import randint
from time import time, sleep
from multiprocessing import Process
from os import getpid def download_task(filename):
print('啟動下載程序,程序號:[%d]'%getpid())
print('開始下載%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main():
start = time()
p1 = Process(target=download_task,args=('python入門.pdf',))
p2 = Process(target=download_task,args=('av.avi',))
p1.start()
p2.start()
p1.join()
p2.join()
# download_task('Python入門.pdf')
# download_task('av.avi')
end = time()
print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__':
main()

多個程序並排執行,總耗時就是最長耗時的那個程序的時間。

大致的執行流程如下圖



多程序的特點是相互獨立,不會共享全域性變數,即在一個程序中對全域性變數修改過後,不會影響另一個程序中的全域性變數。

程序間通訊

from random import randint
from time import time,sleep
from multiprocessing import Process
from os import getpid time_to_download = 3
def download_task(filename):
global time_to_download
time_to_download += 1
print('啟動下載程序,程序號:[%d]'%getpid())
print('開始下載%s...' % filename)
sleep(time_to_download)
print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def download_task2(filename):
global time_to_download
print('啟動下載程序,程序號:[%d]'%getpid())
print('開始下載%s...' % filename)
sleep(time_to_download)
print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main():
start = time()
p1 = Process(target=download_task,args=('python入門.pdf',))
p2 = Process(target=download_task2,args=('av.avi',))
p1.start()
p2.start()
p1.join()
p2.join()
end = time()
print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__':
main()

從執行結果可以看出,兩個程序間的全域性變數無法共享,所以它們是相互獨立的



當然多程序也是可以進行通過一些方法進行資料共享的。可以使用multiprocessing模組的Queue實現多程序之間的資料傳遞,Queue本身是一個訊息列隊程式。

這裡介紹Queue的常用程序通訊的兩種方法:

put 方法用以插入資料到佇列中, put 方法還有兩個可選引數: blocked 和 timeout。如果 blocked 為 True(預設值),並且 timeout 為正值,該方法會阻塞 timeout 指定的時間,直到該佇列有剩餘的空間。如果超時,會丟擲 Queue.full 異常。如果 blocked 為 False,但該 Queue 已滿,會立即丟擲 Queue.full 異常。

get 方法可以從佇列讀取並且刪除一個元素。同樣, get 方法有兩個可選引數: blocked和 timeout。如果 blocked 為 True(預設值),並且 timeout 為正值,那麼在等待時間內沒有取到任何元素,會丟擲 Queue.Empty 異常。如果 blocked 為 False,有兩種情況存在,如果Queue 有一個值可用,則立即返回該值,否則,如果佇列為空,則立即丟擲Queue.Empty 異常。

Queue 佇列實現程序間通訊

from random import randint
from time import time,sleep
from multiprocessing import Process
import multiprocessing
from os import getpid time_to_download = 3
def write(q):
for i in ['python入門','av.avi','java入門']:
q.put(i)
print('啟動寫入程序,程序號:[%d]'%getpid())
print('開始寫入%s...' % i)
sleep(time_to_download) def read(q):
while True:
if not q.empty():
print('啟動讀取程序,程序號:[%d]'%getpid())
print('開始讀取%s...' % q.get())
sleep(time_to_download)
else:
break def main():
q = multiprocessing.Queue()
p1 = Process(target=write,args=(q,))
p2 = Process(target=read,args=(q,))
p1.start()
p1.join()
p2.start()
p2.join() if __name__ == '__main__':
main()

上一個程序寫入的資料通過Queue佇列共享給了下一個程序,然後下一個程序可以直接進行使用,這樣就完成了多程序間的資料共享。

程序池

Pool類可以提供指定數量的程序供使用者呼叫,當有新的請求提交到Pool中時,如果池還沒有滿,就會建立一個新的程序來執行請求。如果池滿,請求就會告知先等待,直到池中有程序結束,才會建立新的程序來執行這些請求。

程序池中常見三個方法:

◆apply:序列

◆apply_async:並行

◆map

多執行緒

from random import randint
from time import time, sleep
from threading import Thread
from os import getpid def download_task(filename):
print('啟動下載程序,程序號:[%d]' % getpid())
print('開始下載%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main():
start = time()
p1 = Thread(target=download_task, args=('python入門.pdf',))
p2 = Thread(target=download_task, args=('av.avi',))
p1.start()
p2.start()
p1.join()
p2.join()
end = time()
print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__':
main()

多執行緒執行因為GIL鎖的存在,實際上執行是進行單執行緒,即一次只執行一個執行緒,然後在切換其他的執行緒進行執行,因為其中切換的時間非常的短,所以看上去依然像是多執行緒一起執行。



通過繼承Thread類的方式來建立自定義的執行緒類,然後再建立執行緒物件並啟動執行緒

from random import randint
from threading import Thread
from time import time, sleep class DownloadTask(Thread):
def __init__(self, filename):
super().__init__()
self._filename = filename def run(self):
print('開始下載%s...'% self._filename)
time_to_download = randint(5,10)
sleep(time_to_download)
print('%s下載完成!耗費了%d秒' %(self._filename, time_to_download)) def main():
start = time()
t1 = DownloadTask('python入門')
t2 = DownloadTask('av.avi')
t1.start()
t2.start()
t1.join()
t2.join()
end = time()
print('共耗費了%.2f秒'%(end - start)) if __name__ == '__main__':
main()

多執行緒使用類還是函式執行的結果完全一致,具體怎麼使用可以結合自己的使用場景。