日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Python?高級教程之線程進程和協程的代碼解析_python

作者:海擁? ? 更新時間: 2022-06-29 編程語言

進程

進程是指在系統中正在運行的一個應用程序,是 CPU 的最小工作單元。

進程 5 種基本狀態

一個進程至少具有 5 種基本狀態:初始態、就緒狀態、等待(阻塞)狀態、執行狀態、終止狀態。

  • 初始狀態:進程剛被創建,由于其他進程正占有CPU資源,所以得不到執行,只能處于初始狀態。
  • 就緒狀態:只有處于就緒狀態的經過調度才能到執行狀態
  • 等待狀態:進程等待某件事件完成
  • 執行狀態:任意時刻處于執行狀態的進程只能有一個(對于單核CPU來講)。
  • 停止狀態:進程結束

進程的特點

  • 動態性:進程是程序的一次執行過程,動態產生,動態消亡。
  • 獨立性:進程是一個能獨立運行的基本單元。是系統分配資源與調度的基本單元。
  • 并發性:任何進程都可以與其他進程并發執行。
  • 結構性:進程由程序、數據和進程控制塊三部分組成。

multiprocessing 是比 fork 更高級的庫,使用 multiprocessing 可以更加輕松的實現多進程程序。

#!/usr/bin/env python
# -*- coding:utf-8 -*- 
from multiprocessing import Process
import threading
import time
def foo(i):
    print 'say hi',i
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

注意:由于進程之間的數據需要各自持有一份,所以創建進程需要的非常大的開銷。并且python不能再Windows下創建進程!

使用多進程的時候,最好是創建和和 CPU 核數相等的進程數。

進程間數據共享

系統中的進程與其他進程共享 CPU 和主存資源,為了更好的管理主存,操作系統提供了一種對主存的抽象概念,即為虛擬存儲器(VM)。它也是一個抽象的概念,它為每一個進程提供了一個假象,即每個進程都在獨占地使用主存。

虛擬存儲器主要提供了三個能力:

  • 將主存看成是一個存儲在磁盤上的高速緩存,在主存中只保存活動區域,并根據需要在磁盤和主存之間來回傳送數據,通過這種方式,更高效地使用主存
  • 為每個進程提供一致的地址空間,從而簡化存儲器管理
  • 保護每個進程的地址空間不被其他進程破壞

由于進程擁有自己獨占的虛擬地址空間,CPU通過地址翻譯將虛擬地址轉換成真實的物理地址,每個進程只能訪問自己的地址空間。因此,在沒有其他機制(進程間通信)的輔助下,進程之間是無法共享數據的。

進程各自持有一份數據,默認無法共享數據。默認的進程之間相互是獨立,如果想讓進程之間數據共享,就得有個特殊的數據結構,這個數據結構就可以理解為他有穿墻的功能 如果你能穿墻的話兩邊就都可以使用了

#!/usr/bin/env python
#coding:utf-8
from multiprocessing import Process
from multiprocessing import Manager
import time
li = []
def foo(i):
    li.append(i)
    print 'say hi',li
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()
print 'ending',li

使用特殊的數據類型,來進行穿墻:

#通過特殊的數據結構:數組(Array)
from multiprocessing import Process,Array
#創建一個只包含數字類型的數組(python中叫列表)
#并且數組是不可變的,在C,或其他語言中,數組是不可變的,之后再python中數組(列表)是可以變得
#當然其他語言中也提供可變的數組
#在C語言中數組和字符串是一樣的,如果定義一個列表,如果可以增加,那么我需要在你內存地址后面再開辟一塊空間,那我給你預留多少呢?
#在python中的list可能用鏈表來做的,我記錄了你前面和后面是誰。列表不是連續的,數組是連續的
'''
上面不是列表是“數組"數組是不可變的,附加內容是為了更好的理解數組!
'''
temp = Array('i', [11,22,33,44]) #這里的i是C語言中的數據結構,通過他來定義你要共享的內容的類型!點進去看~
def Foo(i):
    temp[i] = 100+i
    for item in temp:
        print i,'----->',item
for i in range(2):
    p = Process(target=Foo,args=(i,))
    p.start()
第二種方法:
#方法二:manage.dict()共享數據
from multiprocessing import Process,Manager  #這個特殊的數據類型Manager
manage = Manager()
dic = manage.dict() #這里調用的時候,使用字典,這個字典和咱們python使用方法是一樣的!
def Foo(i):
    dic[i] = 100+i
    print dic.values()
for i in range(2):
    p = Process(target=Foo,args=(i,))
    p.start()
    p.join()

既然進程之間可以進行共享數據,如果多個進程同時修改這個數據是不是就會造成臟數據?是不是就得需要鎖!

進程的鎖和線程的鎖使用方式是非常一樣的知識他們是用的類是在不同地方的。

進程池

進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,如果進程池序列中沒有可供使用的進進程,那么程序就會等待,直到進程池中有可用進程為止。

進程池中有兩個方法:

  • apply
  • apply_async
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from  multiprocessing import Process,Pool
import time
def Foo(i):
    time.sleep(2)
    return i+100
def Bar(arg):
    print arg
pool = Pool(5) #創建一個進程池
#print pool.apply(Foo,(1,))#去進程池里去申請一個進程去執行Foo方法
#print pool.apply_async(func =Foo, args=(1,)).get()
for i in range(10):
    pool.apply_async(func=Foo, args=(i,),callback=Bar)
print 'end'
pool.close()
pool.join()#進程池中進程執行完畢后再關閉,如果注釋,那么程序直接關閉。
'''
apply 主動的去執行
pool.apply_async(func=Foo, args=(i,),callback=Bar) 相當于異步,當申請一個線程之后,執行FOO方法就不管了,執行完之后就在執行callback ,當你執行完之后,在執行一個方法告訴我執行完了
callback 有個函數,這個函數就是操作的Foo函數的返回值!
'''

進程的缺點

無法即時完成的任務帶來大量的上下文切換代價與時間代價。

進程的上下文:當一個進程在執行時,CPU的所有寄存器中的值、進程的狀態以及堆棧中的內容被稱為該進程的上下文。

上下文切換:當內核需要切換到另一個進程時,它需要保存當前進程的所有狀態,即保存當前進程的上下文,以便在再次執行該進程時,能夠得到切換時的狀態并執行下去。

線程

線程的定義

在計算中,進程是正在執行的計算機程序的一個實例。任何進程都有 3 個基本組成部分:

  • 一個可執行程序。
  • 程序所需的相關數據(變量、工作空間、緩沖區等)
  • 程序的執行上下文(進程狀態)

線程是進程中可以調度執行的實體。此外,它是可以在 OS(操作系統)中執行的最小處理單元。

簡而言之,線程是程序中的一系列此類指令,可以獨立于其他代碼執行。為簡單起見,您可以假設線程只是進程的子集!

線程在線程控制塊 (TCB)中包含所有這些信息:

  • 線程標識符:為每個新線程分配唯一 id (TID)
  • 堆棧指針:指向進程中線程的堆棧。堆棧包含線程范圍內的局部變量。
  • 程序計數器:存放線程當前正在執行的指令地址的寄存器。
  • 線程狀態:可以是running、ready、waiting、start或done。
  • 線程的寄存器集:分配給線程進行計算的寄存器。
  • 父進程指針:指向線程所在進程的進程控制塊 (PCB) 的指針。

多線程被定義為處理器同時執行多個線程的能力。

在一個簡單的單核 CPU 中,它是通過線程之間的頻繁切換來實現的。這稱為上下文切換。在上下文切換中,只要發生任何中斷(由于 I/O
或手動設置),就會保存一個線程的狀態并加載另一個線程的狀態。上下文切換發生得如此頻繁,以至于所有線程似乎都在并行運行(這被稱為多任務)。

在 Python 中,threading模塊提供了一個非常簡單直觀的 API,用于在程序中生成多個線程。

使用線程模塊的簡單示例

讓我們考慮一個使用線程模塊的簡單示例:

# Python程序說明線程的概念
# 導入線程模塊
import threading
def print_cube(num):
    """
    打印給定數字立方的函數
    """
    print("立方: {}".format(num * num * num))
def print_square(num):
    """
    打印給定數字平方的函數
    """
    print("平方: {}".format(num * num))
if __name__ == "__main__":
    # creating thread
    t1 = threading.Thread(target=print_square, args=(10,))
    t2 = threading.Thread(target=print_cube, args=(10,))
    # starting thread 1
    t1.start()
    # starting thread 2
    t2.start()
    # 等到線程 1 完全執行
    t1.join()
    # 等到線程 2 完全執行
    t2.join()
    # 兩個線程完全執行
    print("完成!")

平方: 100
立方: 1000
完成!

代碼解析

讓我們試著理解上面的代碼:

  • 要導入線程模塊,我們這樣做:
import threading
  • 要創建一個新線程,我們創建一個Thread類的對象。它需要以下參數:
  • target : 線程要執行的函數
  • args:要傳遞給目標函數的參數

在上面的示例中,我們創建了 2 個具有不同目標函數的線程:

t1 = threading.Thread(target=print_square, args=(10,)) 
t2 = threading.Thread(target=print_cube, args=(10,))

要啟動一個線程,我們使用 Thread 類的 start 方法。

t1.start() 
t2.start()

一旦線程啟動,當前程序(你可以把它想象成一個主線程)也會繼續執行。為了在線程完成之前停止當前程序的執行,我們使用join方法。

t1.join() 
t2.join()

結果,當前程序將首先等待 t1 的完成,然后 t2 。一旦它們完成,則執行當前程序的剩余語句。

協程

協程(Coroutine,又稱微線程,纖程)是一種比線程更加輕量級的存在,協程不是被操作系統內核所管理,而完全是由程序所控制。

我們都熟悉函數,也稱為子例程、過程、子過程等。函數是打包為一個單元以執行特定任務的指令序列。當一個復雜函數的邏輯被分成幾個獨立的步驟,這些步驟本身就是函數時,這些函數被稱為輔助函數或子程序。

Python 中的子程序由負責協調這些子程序的使用的主函數調用。子程序只有一個入口點。 協程是子程序的泛化。它們用于協作式多任務處理,其中一個進程定期或在空閑時自愿放棄(放棄)控制權,以使多個應用程序能夠同時運行。協程和子程序的區別是:

  • 與子程序不同,協程有許多用于暫停和恢復執行的入口點。協程可以暫停其執行并將控制權轉移給其他協程,并且可以從中斷點重新開始執行。
  • 與子程序不同,沒有主函數可以按特定順序調用協程并協調結果。協程是協作的,這意味著它們鏈接在一起形成管道。一個協程可能會使用輸入數據并將其發送給其他處理它的協程。最后,可能會有一個協程來顯示結果。

協程與線程

現在您可能在想協程與線程有何不同,兩者似乎都在做同樣的工作。
在線程的情況下,它是根據調度程序在線程之間切換的操作系統(或運行時環境)。而在協程的情況下,決定何時切換協程的是程序員和編程語言。協程通過程序員在設定點暫停和恢復來協同工作多任務。

Python 協程

在 Python 中,協程類似于生成器,但幾乎沒有額外的方法,而且我們使用yield語句的方式也有細微的變化。生成器為迭代生成數據,而協程也可以使用數據
在 Python 2.5 中,引入了對 yield 語句的輕微修改,現在 yield 也可以用作表達式。例如在作業的右側——

line = (yield)

我們發送給協程的任何值都會被(yield)表達式捕獲并返回。

可以通過send()方法將值發送到協程。例如,考慮這個協程,它打印出帶有前綴“Dear”的名稱。我們將使用 send() 方法將名稱發送到協程。

# 用于演示協程執行的 Python3 程序
def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    while True:
        name = (yield)
        if prefix in name:
            print(name)
# 調用協程,什么都不會發生
corou = print_name("Dear")
# 這將開始執行協程并打印第一行 "Searching prefix..."
# 并將執行推進到第一個 yield 表達式
corou.__next__()
# 發送輸入
corou.send("Haiyong")
corou.send("Dear Haiyong")

輸出:

Searching prefix:Dear
Dear Haiyong

協程的執行

協程的執行類似于生成器。當我們調用協程時,什么都沒有發生,它只在響應next()send ()方法時運行。在上面的例子中可以清楚地看到這一點,因為只有在調用__next__()方法之后,我們的協程才開始執行。在這個調用之后,執行前進到第一個 yield 表達式,現在執行暫停并等待值被發送到 corou 對象。當第一個值被發送給它時,它會檢查前綴和打印名稱(如果存在前綴)。打印完名稱后,它會遍歷循環,直到再次遇到name = (yield)表達式。

關閉協程

協程可能無限期運行,關閉協程使用close()方法。當協程關閉時,它會生成GeneratorExit異常,該異常可以以通常捕獲的方式捕獲。關閉協程后,如果我們嘗試發送值,它將引發StopIteration異常。下面是一個簡單的例子:

# Python3 program for demonstrating
# closing a coroutine
def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try :
        while True:
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("關閉協程!!")
corou = print_name("Dear")
corou.__next__()
corou.send("Haiyong")
corou.send("Dear Haiyong")
corou.close()

輸出:

搜索前綴:Dear?
Dear Haiyong
關閉協程!!

鏈接協程以創建管道

協程可用于設置管道。我們可以使用 send() 方法將協程鏈接在一起并通過管道推送數據。管道需要:

  • 初始源(生產者)派生整個管道。生產者通常不是協程,它只是一個簡單的方法。
  • 一個 sink,它是管道的端點。接收器可能會收集所有數據并顯示它。

以下是一個簡單的鏈接示例

# 用于演示協程鏈接的 Python 程序

def producer(sentence, next_coroutine):
    '''
    producer 只是拆分字符串并將其
    提供給 pattern_filter 協程
    tokens = sentence.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()
def pattern_filter(pattern="ing", next_coroutine=None):
    在接收到的令牌中搜索模式,如果模式匹配,
    將其發送到 print_token() 協程進行打印
    print("Searching for {}".format(pattern))
    try:
        while True:
            token = (yield)
            if pattern in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("過濾完成!!")
def print_token():
    充當接收器,只需打印接收到的令牌
    print("我沉了,我會打印令牌")
            print(token)
        print("打印完成!")
pt = print_token()
pt.__next__()
pf = pattern_filter(next_coroutine = pt)
pf.__next__()
sentence = "Haiyong is running behind a fast moving car"
producer(sentence, pf)

輸出:

我沉了,我會打印令牌
Searching for ing
running?
moving?
過濾完成!
打印完成!

總結

1.線程和協程推薦在 IO 密集型的任務(比如網絡調用)中使用,而在CPU密集型的任務中,表現較差。
2.對于CPU密集型的任務,則需要多個進程,繞開GIL的限制,利用所有可用的CPU核心,提高效率。
3.在高并發下的最佳實踐就是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。

  • CPU 密集型: 多進程
  • IO 密集型: 多線程(協程維護成本較高,而且在讀寫文件方面效率沒有顯著提升)
  • CPU 密集和 IO 密集: 多進程+協程

原文鏈接:https://blog.csdn.net/qq_44273429/article/details/124550361

欄目分類
最近更新