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

歡迎訪問 生活随笔!

生活随笔

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

python

python多线程与GIL

發(fā)布時(shí)間:2025/3/20 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python多线程与GIL 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

1.GIL? ? ??

1.1 為什么要有GIL

1.2 GIL的運(yùn)作方式

1.3?GIL帶來的問題

2.多線線程

2.1 線程的調(diào)度和啟動(dòng)

3.線程構(gòu)造與使用

3.1調(diào)用Thread類構(gòu)造器創(chuàng)建線程

3.2繼承Thread類創(chuàng)建線程類


在學(xué)習(xí)python多線程過程,學(xué)習(xí)一些很不錯(cuò)的文章和教程,這里進(jìn)行摘抄并總結(jié)歸納一下,便于加深自己的理解

1.GIL? ? ??

? ? GIL的全稱是全局解釋器鎖(Global Interpreter Lock),GIL并不是Python的特性,它是在實(shí)現(xiàn)Python解析器(CPython)時(shí)所引入的一個(gè)概念。就好比C++,可以用不同的編譯器來編譯成可執(zhí)行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來執(zhí)行。像其中的JPython就沒有GIL。然而因?yàn)镃Python是大部分環(huán)境下默認(rèn)的Python執(zhí)行環(huán)境。所以在很多人的概念里CPython就是Python,也就想當(dāng)然的把GIL歸結(jié)為Python語言的缺陷。所以這里要先明確一點(diǎn):GIL并不是Python的特性,Python完全可以不依賴于GIL。

1.1 為什么要有GIL

相比于使用更細(xì)粒度的鎖,GIL具有以下優(yōu)點(diǎn):

  • 在單線程任務(wù)中更快;
  • 在多線程任務(wù)中,對(duì)于I/O密集型程序運(yùn)行更快;
  • 在多線程任務(wù)中,對(duì)于用C語言包來實(shí)現(xiàn)CPU密集型任務(wù)的程序運(yùn)行更快;
  • 在寫C擴(kuò)展的時(shí)候更加容易,因?yàn)槌悄阍跀U(kuò)展中允許,否則Python解釋器不會(huì)切換線程;
  • 在打包C庫時(shí)更加容易。我們不用擔(dān)心線程安全性。,因?yàn)槿绻搸觳皇蔷€程安全的,則只需在調(diào)用GIL時(shí)將其鎖定即可。
  • 1.2 GIL的運(yùn)作方式

  • 某個(gè)線程拿到GIL
  • 該線程執(zhí)行代碼,直到達(dá)到了check_interval*
  • 解釋器讓當(dāng)前線程釋放GIL
  • 所有的線程開始競爭GIL
  • 競爭到GIL鎖的線程又從第1步開始執(zhí)行
  • 1.3?GIL帶來的問題

    因?yàn)橛蠫IL的存在,由CPython做解釋器(虛擬機(jī))的多線程Python程序只能利用多核處理器的一個(gè)核來運(yùn)行。

    例如,我們將一個(gè)8線程的JAVA程序運(yùn)行在4核的處理器上,那么每個(gè)核會(huì)運(yùn)行1個(gè)線程,然后利用時(shí)間片輪轉(zhuǎn),輪流運(yùn)行每一個(gè)線程。但是,我們將一個(gè)8線程的Python程序(由CPython作解釋器)運(yùn)行在一個(gè)4核處理器上,那么總共只會(huì)有1個(gè)核在工作,8個(gè)線程都要在這一個(gè)核上面時(shí)間片輪轉(zhuǎn)。

    所以說,用Python做多線程的最佳場景是處理I/O密集型應(yīng)用,例如網(wǎng)絡(luò)爬蟲,因?yàn)镮/O的速度比CPU的速度慢非常多,這樣不同線程的上下文切換(context switch)的時(shí)間就可以忽略不計(jì)了。如果做CPU密集型任務(wù),則使用多進(jìn)程可以更好地利用CPU的多核性能。另外,使用gevent協(xié)程可以避免多線程的上下文切換的問題

    在python中使用都是操作系統(tǒng)級(jí)別的線程,linux中使用的pthread,window使用的是其原生線程

    2.多線線程

    多線程的好處自不必贅述,通過一個(gè)簡單的例子來看一下,參考關(guān)于播放音樂和視頻

    先看看單線程的

    #coding=utf-8 import threading from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)if __name__ == '__main__':print("all start %s" %ctime())music(u'Music')move(u'Vedio')print("all over %s" %ctime())

    輸出如下:

    但實(shí)際上詩就像MV一樣,這兩個(gè)是一般是同時(shí)開展的

    #coding=utf-8 import threading from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)threads = [] t1 = threading.Thread(target=music,args=(u'Music',)) threads.append(t1) t2 = threading.Thread(target=move,args=(u'Vedio',)) threads.append(t2)if __name__ == '__main__':print("all start %s" % ctime())for t in threads:t.setDaemon(True)t.start()t.join()print("all over %s" %ctime())

    ?


    1.join()方法,用于等待線程終止。join()的作用是,在子線程完成運(yùn)行之前,這個(gè)子線程的父線程將一直被阻塞等待子線程結(jié)束

    2.程序中一般有主線程和子線程,兩種線程都執(zhí)行完畢,認(rèn)為是程序執(zhí)行結(jié)束。setDaemon(True)將線程聲明為守護(hù)線程,此類線程的特點(diǎn)是,當(dāng)程序中主線程及所有非守護(hù)線程執(zhí)行結(jié)束時(shí),未執(zhí)行完畢的守護(hù)線程也會(huì)隨之消亡(進(jìn)行死亡狀態(tài)),程序?qū)⒔Y(jié)束運(yùn)行。Python 解釋器的垃圾回收機(jī)制就是守護(hù)線程的典型代表,當(dāng)程序中所有主線程及非守護(hù)線程執(zhí)行完畢后,垃圾回收機(jī)制也就沒有再繼續(xù)執(zhí)行的必要了。需要注意的一點(diǎn)是,線程對(duì)象調(diào)用 daemon 屬性必須在調(diào)用 start() 方法之前,否則 Python 解釋器將報(bào) RuntimeError 錯(cuò)誤

    參考?Python daemon守護(hù)線程詳解


    輸入結(jié)果如下:

    2.1 線程的調(diào)度和啟動(dòng)

    python 中一個(gè)線程對(duì)應(yīng)c語言中一個(gè)線程。GIL使得同一個(gè)時(shí)刻只有一個(gè)線程運(yùn)行在cpu上執(zhí)行字節(jié)碼,無法將多個(gè)線程映射到多個(gè)cpu上執(zhí)行,GIL會(huì)根據(jù)執(zhí)行的字節(jié)碼行數(shù)以及時(shí)間釋放GIL,在遇到IO操作時(shí)會(huì)主動(dòng)釋放。python提供了_thread 和threading 兩個(gè)模塊來支持多線程,其中_thread提供低級(jí)別的,原始的線程支持,以及一個(gè)簡單的鎖,但是一般不建議使用_thread模塊;而threading模塊則提供了功能豐富的多線程支持。

    通過一個(gè)對(duì)一個(gè)全局變量進(jìn)行增減操作,來解釋上述藍(lán)色字體描述

    total = 0 def add(name):global totalfor i in range(10000):total += 1print('當(dāng)前是%s線程,i的值為%s' % (name,total))def desc(name):global totalfor i in range(10000):total -= 1print('當(dāng)前是%s線程,i的值為%s' % (name,total))import threading thread1 = threading.Thread(target=add,args=('thread1',)) thread2 = threading.Thread(target=desc,args=('thread2',))thread1.start() thread2.start()thread1.join() thread2.join()print(total)

    輸入結(jié)果如下所示:

    從輸出結(jié)果來看,并不是等待其中一個(gè)thread跑完了再去跑另一個(gè)線程任務(wù)

    3.線程構(gòu)造與使用

    python 主要提供兩種方式來創(chuàng)建線程:

    • 使用threading模塊的Thread類的構(gòu)造器創(chuàng)建線程
    • 繼承 threading模塊的Thread類創(chuàng)建線程類

    3.1調(diào)用Thread類構(gòu)造器創(chuàng)建線程

    調(diào)用Thread類的構(gòu)造器創(chuàng)建線程,直接調(diào)用threading.Thread類的如下構(gòu)造器創(chuàng)建線程

    __init__(self, group= None, target=None, name= None, args=(), kwargs= None, *, daemon= None)target :指定該線程要調(diào)度的目標(biāo)方法。只傳函數(shù)名,不傳函數(shù),即不加() args :指定一個(gè)元組,以位置參數(shù)的形式為target指定的函數(shù)傳入?yún)?shù)。元組的第一個(gè)參數(shù)傳給target的第一個(gè),以此類推。 kwargs:指定一個(gè)字典,以關(guān)鍵字參數(shù)的形式為target指定的函數(shù)傳入?yún)?shù) daemon: 指定所構(gòu)建的線程是否為后臺(tái)線程。

    調(diào)用Thread類的構(gòu)造器創(chuàng)建并啟動(dòng)多線程的步驟:

  • 調(diào)用Thread類的構(gòu)造器創(chuàng)建線程對(duì)象。在創(chuàng)建線程對(duì)象時(shí)。target參數(shù)指定的函數(shù)將作為線程的執(zhí)行實(shí)體

  • 調(diào)用線程對(duì)象的start()方法啟動(dòng)線程。

  • import threading #定義一個(gè)普通action方法,該方法準(zhǔn)備作為線程的執(zhí)行實(shí)體 def action(max):for i in range(max):#調(diào)用threading模塊的current_thread()函數(shù)獲取當(dāng)前線程#調(diào)用線程對(duì)象的getName()方法獲取單前線程的名字print(threading.current_thread().getName() + " " +str(i)) #下面是主程序 for i in range(100):#調(diào)用threading模塊的current_thread()函數(shù)獲取當(dāng)前線程print(threading.current_thread().getName() + " " +str(i))if i == 20:t1 = threading.Thread(target=action, args=(100,))t1.start()t2 = threading.Thread(target =action, args=(100,))t2.start() print("主線程執(zhí)行完成!")

    執(zhí)行結(jié)果如下:

    可以看到在循環(huán)變量達(dá)到20時(shí),創(chuàng)建并且啟動(dòng)了兩個(gè)新線程。所以此時(shí)一共有三個(gè)線程,但是三個(gè)線程之間的執(zhí)行沒有沒有先后順序,他們的執(zhí)行方式為Thread-1執(zhí)行一段時(shí)間Thread-2或MainThread獲得CPU執(zhí)行一段時(shí)間。

    • threading.current_thread():它是threading模塊的函數(shù),該函數(shù)總是返回當(dāng)前正在執(zhí)行的線程對(duì)象
    • getName() 它是Thread類的實(shí)例方法,該方法返回調(diào)用它的線程名字
      -setName() 方法可以為線程設(shè)置名字,getName和setName 兩個(gè)方法可通過name屬性來代替。

    3.2繼承Thread類創(chuàng)建線程類

    • 定義Thread類的子類,并重寫該類的run()方法。run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱為線程執(zhí)行體。

    • 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建線程對(duì)象

    • 調(diào)用線程對(duì)象的start()方法來啟動(dòng)線程

    import threading import timeclass GetDetailHtml(threading.Thread):def __init__(self, name):super().__init__(name=name)def run(self):print("get detail html started")time.sleep(2)print("get detail html end")class GetDetailUrl(threading.Thread):def __init__(self, name):super().__init__(name=name)def run(self):print("get detail url started")time.sleep(2)print("get detail url end")if __name__ == "__main__":thread1 = GetDetailHtml("get_detail_html")thread2 = GetDetailUrl("get_detail_url")start_time = time.time()thread1.start()thread2.start()"""join進(jìn)行線程阻塞,只有兩個(gè)線程都執(zhí)行完成后才執(zhí)行后邊的"""thread1.join()thread2.join()print("last time :{}".format(time.time() - start_time))

    輸出結(jié)果如下:

    ?

    參考 python中的GIL詳解

    參考?python 多線程

    參考?python并發(fā)編程

    總結(jié)

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

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