日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python主线程执行_python 并发执行之多线程

發(fā)布時間:2025/4/5 python 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python主线程执行_python 并发执行之多线程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

正常情況下,我們在啟動一個程序的時候。這個程序會先啟動一個進(jìn)程,啟動之后這個進(jìn)程會拉起來一個線程。這個線程再去處理事務(wù)。也就是說真正干活的是線程,進(jìn)程這玩意只負(fù)責(zé)向系統(tǒng)要內(nèi)存,要資源但是進(jìn)程自己是不干活的。默認(rèn)情況下只有一個進(jìn)程只會拉起來一個線程。

多線程顧名思義,就是同樣在一個進(jìn)程的情況同時拉起來多個線程。上面說了,真正干活的是線程。進(jìn)程與線程的關(guān)系就像是工廠和工人的關(guān)系。那么現(xiàn)在工廠還是一個,但是干活的工人多了。那么效率自然就提高了。因為只有一個進(jìn)程,所以多線程在提高效率的同時,并沒有向系統(tǒng)伸手要更多的內(nèi)存資源。因此使用起來性價比還是很高的。但是多線程雖然不更多的消耗內(nèi)存,但是每個線程卻需要CPU的的參與。

相當(dāng)于工廠雖然廠房就一間,可以有很多的工人干活。但是這些工人怎么干活還得靠廠長來指揮。工人太多了,廠長忙不過來安排一樣效率不高。所以工人(線程)的數(shù)量最好還是在廠長(cpu)的能力(內(nèi)核數(shù))范圍之內(nèi)比較好。

在python中多線程的實現(xiàn)方式有兩種,我的總結(jié)就是一種是函數(shù)形式的。一種是通過自己創(chuàng)建一個類并繼承threading.Thread類來實現(xiàn)的。其實關(guān)于多線程用到模塊,也是有兩種。一種是thread。這個模塊是最原始的多線程模塊,但是這個模塊據(jù)說是比較low的。threading模塊封裝了thread模塊,反正就是比較高級,反正就是沒人用thread寫程序,都用threading!!記住就好~

下面先來介紹第一種,也是我認(rèn)為比較簡單的一種函數(shù)形式的。

先舉個例子看下面的代碼import?time

def?haha(max_num):

"""

隨便定義一個函數(shù),要求用戶輸入一個要打印數(shù)字的最大范圍

輸入之后就會從0開始打印,直到用戶輸入的范圍值

"""

for?i?in?range(max_num):

"""

每次打印一個數(shù)字前要間隔1秒,那么打印10個數(shù)就要耗時10秒

"""

time.sleep(1)

print?i

for?x?in?range(3):

haha(10)

上面的代碼沒什么難度,只是展現(xiàn)一下如果順序執(zhí)行函數(shù)haha()。執(zhí)行三遍需要耗時30秒。因為程序要執(zhí)行完第一個循環(huán)之后才會執(zhí)行第二個循環(huán)。時間是累加的。

現(xiàn)在我們引入多線程的方式執(zhí)行。看看會不會有什么變化。import?threading

import?time

def?haha(max_num):

"""

隨便定義一個函數(shù),要求用戶輸入一個要打印數(shù)字的最大范圍

輸入之后就會從0開始打印,直到用戶輸入的最大范圍

"""

for?i?in?range(max_num):

"""

每次打印一個數(shù)字要間隔1秒,那么打印10個數(shù)就要耗時10秒

"""

time.sleep(1)

print?i

for?x?in?range(3):

"""

這里的rang(3)是要依次啟動三個線程,每個線程都調(diào)用函數(shù)haha()

第一個線程啟動執(zhí)行之后,馬上啟動第二個線程再次執(zhí)行。最后也相當(dāng)

函數(shù)執(zhí)行了3次

"""

#通過threading.Thread方法實例化多線程類

#target后面跟的是函數(shù)的名稱但是不要帶括號也不填寫參數(shù)

#args后面的內(nèi)容才是要傳遞給函數(shù)haha()的參數(shù)。切記參數(shù)一定要以數(shù)組的形式填寫不然會報錯。

t=threading.Thread(target=haha,args=(10,))

#將線程設(shè)置為守護(hù)線程

t.setDaemon(True)

#線程準(zhǔn)備就緒,隨時等候cpu調(diào)度

t.start()

執(zhí)行的結(jié)果是。。。。。。。。。。。。。。什么都沒有發(fā)生!!!!沒有任何輸出。什么情況??!!!是不是代碼有錯誤??!

其實問題就出在t.setDaemon(True) ?這一句上。默認(rèn)不寫這句或者說默認(rèn)設(shè)置的情況這一句應(yīng)該是

t.setDaemon(False)這樣子的。那這一句是什么意思呢?

setDaemon ? 設(shè)置為后臺線程或前臺線程(默認(rèn))

如果是后臺線程,主線程執(zhí)行過程中,后臺線程也在進(jìn)行,主線程執(zhí)行完畢后,后臺線程不 ? ? ? ? ? ? 論成功與否,均停止

如果是前臺線程,主線程執(zhí)行過程中,前臺線程也在進(jìn)行,主線程執(zhí)行完畢后,等待前臺線 ? ? ? ? ? ? 程也執(zhí)行完成后,程序停止

這些什么前臺、后臺、主線程都是什么玩意?聽著是不是特別暈?其實沒有這么復(fù)雜。簡單理解就是如果這個參數(shù)是True,就表示程序流程跑完之后直接就關(guān)閉線程然后退出了,根本不管線程是否執(zhí)行完。從上面的例子可以看出來,我們每執(zhí)行一遍函數(shù)haha()最少也得耗時10秒,哪怕是打印第一個數(shù)字出來也得停頓1秒之后才會輸出。但是程序流程就是拉起來三個線程就結(jié)束了。執(zhí)行啟動線程的3次for循環(huán)可用不了10秒,1秒都用不到就結(jié)束了。所以就出現(xiàn)了我們看到的結(jié)果,程序拉起來3個線程,就結(jié)束了主線程但是此時線程調(diào)用的函數(shù)haha()還沒來得及輸出呢,就被迫跟著程序一起結(jié)束了。

既然找到了原因,我們就來修改一下代碼。把礙事的那部分置為默認(rèn)值或者干脆不寫這一行import?threading

import?time

def?haha(max_num):

for?i?in?range(max_num):

time.sleep(1)

print?i

for?x?in?range(3):

t=threading.Thread(target=haha,args=(5,))

#也可以干脆不寫這一行

t.setDaemon(False)

t.start()

現(xiàn)在運行,就可以看到看起來很亂的執(zhí)行結(jié)果0

00

1

11

2

2

2

3

3

3

4

4

4

其實這就是三個線程并行運行同時輸出,所以把結(jié)果都輸出到一起引起。正是這種亂才整明白了確實三個函數(shù)haha()在同時運行。

如果想讓結(jié)果看起來規(guī)則一些可以考慮使用join()方法import?threading

import?time

def?haha(max_num):

for?i?in?range(max_num):

time.sleep(1)

print?i

for?x?in?range(3):

t=threading.Thread(target=haha,args=(5,))

t.start()

#通過join方法讓線程逐條執(zhí)行

t.join()

這樣執(zhí)行的結(jié)果看起來就美觀了0

1

2

3

4

0

1

2

3

4

0

1

2

3

4

就像注釋所說的那樣,美觀是沒問題了。可是這樣的話雖然創(chuàng)建了多個線程,每個線程卻是依次執(zhí)行的。沒有了并行還要多線程干嘛。這樣和最上面寫的串行執(zhí)行例子就一個效果了。因此join方法不能隨便亂用的。

可是既然有了join()方法它總得有用吧?設(shè)計出來肯定不是為了擺著看的。現(xiàn)在我們再修改一下代碼,看看join()方法到底怎么正確使用。import?threading

import?time

def?haha(max_num):

for?i?in?range(max_num):

time.sleep(1)

print?i

"""

創(chuàng)建一個列表,用于存儲要啟動多線程的實例

"""

threads=[]

for?x?in?range(3):

t=threading.Thread(target=haha,args=(5,))

#把多線程的實例追加入列表,要啟動幾個線程就追加幾個實例

threads.append(t)

for?thr?in?threads:

#把列表中的實例遍歷出來后,調(diào)用start()方法以線程啟動運行

thr.start()

for?thr?in?threads:

"""

isAlive()方法可以返回True或False,用來判斷是否還有沒有運行結(jié)束

的線程。如果有的話就讓主線程等待線程結(jié)束之后最后再結(jié)束。

"""

if?thr.isAlive():

thr.join()

上面學(xué)習(xí)setDaemon()方法的時候我們知道,主線程其實就相當(dāng)于程序的主運行流程。那么程序運行的時候最先啟動的一定就是主線程,主線程負(fù)責(zé)拉起子線程用于干活。我們的例子中運行函數(shù)haha()線程其實都是子線程。因此可以說多線程其實就是多個子線程。那么程序運行完最后一個退出的也肯定就是主線程。因此上例中最后再遍歷一個遍threads列表的目的就是查看還是否有沒有退出的子線程,只要還有子線程是活的,沒有退出。就通過join()方法強制程序流程不可以走到主線程退出的那個步驟。只有等子線程都退出之后,才能根據(jù)join()方法的規(guī)則順序執(zhí)行到主線程退出的步驟。

第二種創(chuàng)建多線程的方式就是通過自定義一個類來實現(xiàn)的。import?threading

import?time

class?haha(threading.Thread):

"""

自定義一個類haha,必須要繼承threading.Thread,下面必須要重寫一個run()方法。

把要執(zhí)行的函數(shù)寫到run()方法里。如果沒有run()方法就會報錯。其實這個類的作用就是

通過haha類里面的run()方法來定義每個啟動的子線程要執(zhí)行的函數(shù)內(nèi)容。

"""

def?__init__(self,max_num):

threading.Thread.__init__(self)

self.max_num=max_num

def?run(self):

for?i?in?range(self.max_num):

time.sleep(1)

print?i

if?__name__=='__main__':

threads=[]

for?x?in?range(3):

"""

只是這里和函數(shù)方式有點區(qū)別,因為haha類繼承了threading.Thread,所以通過haha類的實例化

就相當(dāng)于調(diào)用了多線程的實例化。剩下的操作就和函數(shù)方式一個樣子了。

"""

t=haha(5)

threads.append(t)

for?thr?in?threads:

thr.start()

for?thr?in?threads:

if?thr.isAlive():

thr.join()

以上就是實現(xiàn)多線程的兩種方式,根據(jù)個人喜好選擇就好。沒什么本質(zhì)區(qū)別。

下面介紹一下線程鎖,先看下面一段代碼import?threading

#定義一個變量

gnum=0

def?work(max_number):

for?i?in?range(max_number):

print?i

def?mylock():

global?gnum

"""

這個函數(shù)運行的時候需要先運行一下函數(shù)work()

執(zhí)行完之后將全局的gnum+1

"""

work(10)

#將變量聲明為全局變量

gnum=gnum+1

print?'gnum?is?',gnum

for?x?in?range(5):

"""

同時啟動5個現(xiàn)成運行mylock()函數(shù)

"""

t=threading.Thread(target=mylock)

t.start()

上面的例子看起來也不難,目的就是在執(zhí)行g(shù)num+1之前先運行另外一個耗時的函數(shù)而已。因為我們啟動了5個線程同時運行,理論上運行流程應(yīng)該是第一個線程運行完成之后gnum+1=1,此時第二個線程也運行完了在gnum=1的基礎(chǔ)上再加1,使gnum=2。以此類推,最后當(dāng)5個線程運行完了的時候gnum應(yīng)該等于5。但是實際運行的時候并不是我們想象的那個樣子!!!!!

真實的情況是當(dāng)我們第一個線程運行的時候gnum=0,運行一個耗時的work()函數(shù)。因為線程是并發(fā)執(zhí)行的,那這時候在第一個work()還沒運行完的情況下,第二個線程又啟動開始運行了。第一個線程沒有運行完的情況下,是不會執(zhí)行g(shù)num+1操作的。此時對第二個線程來說依舊是gnum=0。之后第一個線程結(jié)束的時候gnum經(jīng)過自加1變成了gnum=1,可是第二個線程還是當(dāng)初取值的時候還是按照gnum=0來進(jìn)行的自加運算。所以第二次運算的結(jié)果很有可能還是gnum=1。沒有達(dá)到我們理想的gnum=2的效果。

從這里就可以看出來,如果多線程執(zhí)行的任務(wù)互不相干那自然什么事情都沒有。一旦要利用多線程多同一個變量進(jìn)行操作的時候,因為線程是并發(fā)執(zhí)行的。所以很有很可能同時修改變量,導(dǎo)致最終結(jié)果不符合我們的預(yù)期。

遇到這種情況一個方案就是用我們上面跳到j(luò)oin方法,讓線程依次運行。這樣同時就只有一個線程在修改變量,不會出現(xiàn)混亂。但是問題還是一樣多線程并發(fā)的效果就沒有了。肯定不可取。第二個

方案就是使用線程鎖。什么是線程鎖呢?就是在多個線程同時操作一個資源的時候,哪個線程先操作。哪個線程就先鎖定這個資源。直到這個線程操作結(jié)束打開鎖之后,其他的線程才能再操作。這就叫做線程安全,也就是線程鎖。聽起來好像和join()方法有點類似。其實還是有區(qū)別的,先來看看加了線程鎖的代碼。import?threading

gnum=0

lock=threading.RLock()

def?work(max_number):

for?i?in?range(max_number):

print?i

def?mylock():

work(10)

#在操作gnum之前先上鎖

#acquire()的括號里可以定義鎖定的timeout時間,超過這個時間就自動打開鎖

lock.acquire()

global?gnum

gnum=gnum+1

#操作結(jié)束之后再打開鎖

lock.release()

print?'gnum?is?',gnum

for?x?in?range(5):

t=threading.Thread(target=mylock)

t.start()

上從面的代碼可以看出區(qū)別,join()方法是對整個線程做限制。而線程鎖lock.acquire是在線程執(zhí)行過程中對某一部分進(jìn)行鎖限制。例子中被啟動的各個線程還是可以并行運行work()這個比較耗時的函數(shù),只是在gnum的處理上才會受到鎖的限制而已。這樣就解決了多線程同時操作一個資源引發(fā)錯誤數(shù)據(jù)的問題。另外一個要注意的就是threading.RLock()也是Lock()的高級用法,用這個高級的就可以了。

多線程的event事件

一般情況下,多線程在創(chuàng)建之后就開始立即投入工作。沒有任何停頓。但是有時候我們也許并不希望如此。比如我們要寫一個爬蟲程序。在爬取網(wǎng)頁之前,我希望先ping一下這個網(wǎng)頁。看看這個網(wǎng)頁網(wǎng)頁是否可以ping通。如果通了就釋放線程去爬取內(nèi)容。如果不通就去測試下一個網(wǎng)頁。所以python線程的事件用于主線程控制其他線程的執(zhí)行,事件主要提供了三個方法?set、wait、clear。其中event.wait()相當(dāng)于一個全局的標(biāo)識,程序根據(jù)event.set()和event.clear()兩個方法分別定制這個全局Flag的值為True或者Flase。當(dāng)Flag=True的時候就相當(dāng)于收到釋放所有線程的信號。看下面一個列子import?threading

def?do(event):

print?'start'

#函數(shù)執(zhí)行到這里等待信號放行信號

event.wait()

#收到放行信號后執(zhí)行下面的語句

print?'execute'

#實例化threading.Event()事件

event_obj?=?threading.Event()

for?i?in?range(10):

t?=?threading.Thread(target=do,?args=(event_obj,))

t.start()

#先將Flag標(biāo)識置為False

event_obj.clear()

inp?=?raw_input('input:')

#如果用戶輸入'true'就像wait()發(fā)送放行信號

if?inp?==?'true':

event_obj.set()

這樣就完成通過set()和clear()方法控制線程運行的目的

最后再簡單介紹一下GIL

GIL是python的全局解釋器鎖的簡稱。這個鎖是干什么用的呢?說白了就是限制python解釋調(diào)用cpu內(nèi)核之用的。多線程理論上可以同時調(diào)用多個cpu內(nèi)核同時工作,比如java語言就可以做到。但是python因為GIL的存在,同一時間只有一條進(jìn)程在cpu內(nèi)核中進(jìn)行處理。雖然我們可以看到多線程并發(fā)運行,但是那只是因為cpu內(nèi)核通過上下文的切換快速將多個線程來回執(zhí)行造成的假象。python和java那種可以真正調(diào)用多核心多線程的語言,在效率上還是有差異的。這個就是python一直被人詬病的GIL鎖。

總結(jié)

以上是生活随笔為你收集整理的python主线程执行_python 并发执行之多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。