欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python進(jìn)階之多線程的實(shí)現(xiàn)方法總結(jié)

 更新時(shí)間:2023年04月14日 10:30:56   作者:生魚(yú)同學(xué)  
在python中主要有兩種實(shí)現(xiàn)多線程的方式:通過(guò)threading.Thread?()?方法創(chuàng)建線程和通過(guò)繼承?threading.Thread?類的繼承重寫(xiě)run方法,接下來(lái)我們分別說(shuō)一下多線程的兩種實(shí)現(xiàn)形式吧

線程

想要理解線程的含義,首先我們先看一下百度百科的定義:

線程(英語(yǔ):thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。

簡(jiǎn)單來(lái)講,當(dāng)你打開(kāi)電腦中的一個(gè)應(yīng)用程序,其實(shí)此時(shí)計(jì)算機(jī)就為你創(chuàng)建了一個(gè)進(jìn)程,系統(tǒng)會(huì)為其進(jìn)行資源分配并且對(duì)其進(jìn)行調(diào)度。而線程就是比進(jìn)程還要小的單位,多個(gè)線程完成不同的工作組成了我們宏觀上能夠得到響應(yīng)的工作結(jié)果。

舉個(gè)例子,進(jìn)程就像一個(gè)大的工廠,工廠中有很多機(jī)床設(shè)備和場(chǎng)地。而不同的線程就像工廠中工作的工人,工廠為其分配不同的工作來(lái)完成一個(gè)最終的生產(chǎn)目標(biāo)。我們可以指派不同的工人做不同的工作或增加工人提高我們的生產(chǎn)效率。

在編程中,線程可以由我們啟用幫助我們完成不同的工作實(shí)現(xiàn)多線程并發(fā),提高我們的代碼效率。

Python中的多線程

在python中主要有兩種實(shí)現(xiàn)多線程的方式:

  • 通過(guò)threading.Thread () 方法創(chuàng)建線程
  • 通過(guò)繼承 threading.Thread 類的繼承重寫(xiě)run方法

接下來(lái)我們分別說(shuō)一下多線程的兩種實(shí)現(xiàn)形式。

threading.Thread () 創(chuàng)建線程

為了更直觀的理解這個(gè)過(guò)程,首先我們先編寫(xiě)一個(gè)正常的函數(shù),完成倒數(shù)5個(gè)數(shù)的功能,其中間隔一秒鐘。

def fuc():
    for i in range(5):
        time.sleep(1)

在主函數(shù)中,我們調(diào)用Thread()來(lái)實(shí)例化兩個(gè)線程,讓他們同時(shí)運(yùn)行。

if __name__ == '__main__':
    t1 = threading.Thread(target=fuc, args=(1,), daemon=True)
    t2 = threading.Thread(target=fuc, args=(2,), daemon=True)
    t2.start()
    t1.start()

整體代碼如下所示:

import threading
import time


def fuc():
    for i in range(5):
        time.sleep(1)


if __name__ == '__main__':
    t1 = threading.Thread(target=fuc)
    t2 = threading.Thread(target=fuc)
    t2.start()
    t1.start()

我們先不討論調(diào)用的函數(shù)以及傳入的參數(shù),先來(lái)看一下運(yùn)行效果:

0
0
11
22
33
44

可以看到,兩個(gè)打印的結(jié)果基本上是同時(shí)出現(xiàn)的,并且出現(xiàn)了混合的情況,證明兩個(gè)打印的函數(shù)正在同時(shí)進(jìn)行。

接下來(lái)我們就來(lái)介紹一下類的初始化參數(shù)以及我們調(diào)用的函數(shù):

thread.Thread(group=Nore,targt=None,args=(),kwargs={},*,daemon=None)

在該類中主要由以下幾個(gè)參數(shù)組成:

  • group:與ThreadGroup類相關(guān),一般不使用。
  • target:線程調(diào)用的對(duì)象,就是目標(biāo)函數(shù),在上述的例子中我們傳入的是我們編寫(xiě)的函數(shù)fuc。
  • name:線程的名字,默認(rèn)是Tread-x。
  • args:為目標(biāo)函數(shù)傳遞關(guān)鍵字參數(shù),字典。
  • daemon:用來(lái)設(shè)置線程是否隨主線程退出而退出,涉及到主線程相關(guān)知識(shí),我們稍后介紹。

接下來(lái)介紹我們常用的幾個(gè)方法:

  • run():表示線程啟動(dòng)的活動(dòng),在第二種繼承寫(xiě)法中會(huì)用到。
  • start():激活線程,使其能夠被調(diào)度。
  • join():等待至線程終止,這個(gè)方法涉及到主線程的知識(shí),我們稍后介紹。
  • isAlive():返回線程是否活動(dòng)。
  • getName():返回線程名稱。
  • setName() : 設(shè)置線程名稱。

接下來(lái),我們使用上述參數(shù)更改示例,讓函數(shù)獲取一個(gè)參數(shù),并為不同的線程設(shè)置名字。代碼如下:

import threading
import time


def fuc(num):
    for i in range(5):
        print('接收到參數(shù){}:'.format(num), i)
        time.sleep(1)


if __name__ == '__main__':
	# 傳入?yún)?shù)及名字
    t1 = threading.Thread(target=fuc, args=(1,), name='t1')
    t2 = threading.Thread(target=fuc, args=(2,), name='t2')
    t1.start()
    print(t1.getName(), '開(kāi)始運(yùn)行...')
    t2.start()
    print(t2.getName(), '開(kāi)始運(yùn)行...')

運(yùn)行結(jié)果如下:

接收到參數(shù)1:t1 開(kāi)始運(yùn)行... 
0
接收到參數(shù)2: t20 開(kāi)始運(yùn)行...

接收到參數(shù)1:接收到參數(shù)2: 1 
1
接收到參數(shù)1:接收到參數(shù)2:  2
2
接收到參數(shù)1:接收到參數(shù)2:  33

接收到參數(shù)1:接收到參數(shù)2:  4
4

可以看到,雖然結(jié)果很混亂,但是我們傳入的參數(shù)以及獲取的名字都被打印出來(lái)了。

另外,這里有兩個(gè)注意:

  • trgat參數(shù)接受的是函數(shù)名字不需要加括號(hào)。
  • args傳入的執(zhí)行函數(shù)參數(shù)要加括號(hào)和逗號(hào),保證其是一個(gè)元組。

繼承 threading.Thread 類的線程創(chuàng)建

在上面的例子中,我們已經(jīng)理解了多線程的一種創(chuàng)建方法。接下來(lái)我們來(lái)介紹第二種方法,這也是眾多大佬很喜歡的一種方法,通過(guò)繼承 threading.Thread 類的線程創(chuàng)建。

class MyThread(threading.Thread):
    def run(self) -> None:
        for i in range(5):
            print(i)
            time.sleep(1)


if __name__ == '__main__':
    t1 = MyThread(name='t1')
    t2 = MyThread(name='t2')
    t1.start()
    t2.start()

運(yùn)行結(jié)果如下:

0
0
11
22
33
44

注意:這里調(diào)用的是start方法而不是run方法,否則會(huì)編程單線程執(zhí)行。

主線程

在了解了多線程的編程方法之后,我們來(lái)介紹一下主線程及相關(guān)參數(shù)和方法。

在我們執(zhí)行多線程程序的過(guò)程中,存在一個(gè)主線程,而我們開(kāi)辟的其他線程其實(shí)都是它的子線程。由主線程主導(dǎo)的工作有以下兩種情況:

  • 由于主線程結(jié)束了,強(qiáng)制停止其它線程的工作,但此時(shí)其他線程有可能還沒(méi)有結(jié)束自己的工作。
  • 主線程結(jié)束后,等待其他線程結(jié)束工作,再停止所有線程的工作。

可以簡(jiǎn)單地理解為包工頭,它是這些線程的頭子!其從微觀角度上講掌管了一定的工作流程,它可以選擇是否等待其它工人結(jié)束工作再結(jié)束整個(gè)工作。

而我們可以使用參數(shù)或者方法控制這個(gè)過(guò)程。

使用daemon參數(shù)控制過(guò)程

在上邊的函數(shù)參數(shù)介紹中,提到了daemon參數(shù),其為False時(shí),線程不會(huì)隨主線程結(jié)束而退出,主線程會(huì)等待其結(jié)束后再退出。而為T(mén)rue時(shí)則不論子線程是否完成了相關(guān)工作都會(huì)直接退出。

接下來(lái)我們看兩個(gè)示例,我們修改剛才的示例代碼的daemon參數(shù)為T(mén)rue,表示不論子線程是否完成了工作都強(qiáng)制退出。

import threading
import time


def fuc(num):
    for i in range(5):
        print('接收到參數(shù){}:'.format(num), i)
        time.sleep(1)


if __name__ == '__main__':
    t1 = threading.Thread(target=fuc, args=(1,), name='t1', daemon=True)
    t2 = threading.Thread(target=fuc, args=(2,), name='t2', daemon=True)
    t1.start()
    print(t1.getName(), '開(kāi)始運(yùn)行...')
    t2.start()
    print(t2.getName(), '開(kāi)始運(yùn)行...')
    print("我是主線程,都給我停下!")

結(jié)果如下:

接收到參數(shù)1:t1 0 
開(kāi)始運(yùn)行...
接收到參數(shù)2:t2  0
開(kāi)始運(yùn)行...
我是主線程,都給我停下!

可以看到,子線程的倒數(shù)還沒(méi)有結(jié)束,由于主線程結(jié)束了,所有線程一起結(jié)束了。 這里要注意以下幾點(diǎn):

  • daemon屬性必須在start( )之前設(shè)置。
  • 從主線程創(chuàng)建的所有線程不設(shè)置daemon屬性,則默認(rèn)都是daemon=False。

使用.join()阻塞線程

除此之外,我們還可以調(diào)用.join()方法阻塞線程,調(diào)用該方法的時(shí)候,該方法的調(diào)用者線程結(jié)束后程序才會(huì)終止。

#timeout參數(shù)表明等待的時(shí)長(zhǎng),不設(shè)置該參數(shù)則默認(rèn)為一直等待。
join(timeout-=None)

我們來(lái)看下面這個(gè)示例,我們更改了兩個(gè)函數(shù)的倒計(jì)時(shí)時(shí)間,使第一個(gè)線程的倒計(jì)時(shí)時(shí)間更長(zhǎng),并對(duì)第二個(gè)線程進(jìn)行了阻塞操作。代碼如下:

import threading
import time


def fuc1():
    for i in range(10):
        print(i)
        time.sleep(1)


def fuc2():
    for i in range(5):
        print(i)
        time.sleep(1)


if __name__ == '__main__':
    t1 = threading.Thread(target=fuc1, name='t1', daemon=True)
    t2 = threading.Thread(target=fuc2, name='t2', daemon=True)
    t1.start()
    print(t1.getName(), '開(kāi)始運(yùn)行...')
    print('我是二兒子,等等我!')
    t2.start()
    print(t2.getName(), '開(kāi)始運(yùn)行...')
    t2.join()
    print("我是主線程,都給我停下!")

結(jié)果如下:

0t1
開(kāi)始運(yùn)行...
我是二兒子,等等我!
0t2 
開(kāi)始運(yùn)行...
11
22
33
44

我是主線程,都給我停下!5

我們可以看到,上述代碼中線程一還沒(méi)有結(jié)束倒數(shù)十個(gè)數(shù),程序就結(jié)束了。在此過(guò)程中,主線程只等待了第二個(gè)線程結(jié)束,整個(gè)程序就結(jié)束了。

線程同步

在多個(gè)線程同步運(yùn)行的情況下,會(huì)出現(xiàn)多個(gè)線程同時(shí)操作一個(gè)數(shù)據(jù)的情況。如果兩個(gè)線程同時(shí)操作同一個(gè)變量的話,很容易出現(xiàn)混亂的情況。所以,我們需要一個(gè)工具來(lái)確保在同一時(shí)間只能有一個(gè)線程處理數(shù)據(jù)。

線程類提供了鎖來(lái)解決問(wèn)題,當(dāng)線程申請(qǐng)?zhí)幚砟硞€(gè)數(shù)據(jù)時(shí)申請(qǐng)一個(gè)鎖來(lái)控制住當(dāng)前數(shù)據(jù),結(jié)束處理時(shí)即將鎖釋放。

threading中的鎖

python的threading中為我們提供了RLock鎖來(lái)解決多線程同時(shí)處理一個(gè)數(shù)據(jù)的問(wèn)題。在某個(gè)時(shí)刻,我們可以讓線程申請(qǐng)鎖來(lái)保護(hù)數(shù)據(jù)此時(shí)只能供該線程使用。

為了更好的理解該過(guò)程,我們定義一個(gè)全局變量,讓每一個(gè)線程都對(duì)其操作但不設(shè)置鎖,觀察變量的變化:

R_LOCK = threading.Lock()
COUNT = 100


class MyThread(threading.Thread):
    def run(self) -> None:
        global COUNT
        #R_LOCK.acquire()
        COUNT -= 10
        time.sleep(1)
        print(self.getName(), COUNT)
        #R_LOCK.release()


if __name__ == '__main__':
    threads = [MyThread() for i in range(10)]
    for t in threads:
        t.start()

結(jié)果如下:

Thread-3Thread-10  0Thread-8Thread-7 0Thread-6 0Thread-5Thread-9
Thread-1 0Thread-2 00  0
Thread-4 000

可以看到,我們的數(shù)據(jù)發(fā)生了異常,這并不是我們想要得到的結(jié)果,若把鎖給關(guān)閉注釋讓其正常運(yùn)行可以看到以下的正常結(jié)果:

Thread-1 90
Thread-2 80
Thread-3 70
Thread-4 60
Thread-5 50
Thread-6 40
Thread-7 30
Thread-8 20
Thread-9 10
Thread-10 0

結(jié)語(yǔ)

多線程編程是一個(gè)非常重要的編程思想,理解多線程編程有助于我們更好的理解設(shè)計(jì)模式。

當(dāng)然,python中的編程并不是真正的多線程執(zhí)行,這涉及到GIL全局解釋鎖相關(guān)的知識(shí)。所以其針對(duì)CPU密集型任務(wù)來(lái)說(shuō)并沒(méi)有很好的效果,接下來(lái)我將會(huì)更新相關(guān)的內(nèi)容進(jìn)行更多的說(shuō)明。

以上就是Python進(jìn)階之多線程的實(shí)現(xiàn)方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Python多線程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論