亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

python多線程詳解

系統(tǒng) 1765 0

目錄

  • python多線程詳解
    • 一、線程介紹
      • 什么是線程
      • 為什么要使用多線程
    • 二、線程實現(xiàn)
      • threading模塊
      • 自定義線程
      • 守護線程
      • 主線程等待子線程結(jié)束
      • 多線程共享全局變量
      • 互斥鎖
      • 遞歸鎖
      • 信號量(BoundedSemaphore類)
      • 事件(Event類)
    • 三、GIL(Global Interpreter Lock)全局解釋器鎖

python多線程詳解

一、線程介紹

什么是線程

線程(Thread)也叫輕量級進程,是操作系統(tǒng)能夠進行運算調(diào)度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。線程自己不擁有系統(tǒng)資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創(chuàng)建和撤消另一個線程,同一進程中的多個線程之間可以并發(fā)執(zhí)行。

為什么要使用多線程

線程在程序中是獨立的、并發(fā)的執(zhí)行流。與分隔的進程相比,進程中線程之間的隔離程度要小,它們共享內(nèi)存、文件句柄和其他進程應有的狀態(tài)。

因為線程的劃分尺度小于進程,使得多線程程序的并發(fā)性高。進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率。

線程比進程具有更高的性能,這是由于同一個進程中的線程都有共性多個線程共享同一個進程的虛擬空間。線程共享的環(huán)境包括進程代碼段、進程的公有數(shù)據(jù)等,利用這些共享的數(shù)據(jù),線程之間很容易實現(xiàn)通信。

操作系統(tǒng)在創(chuàng)建進程時,必須為該進程分配獨立的內(nèi)存空間,并分配大量的相關(guān)資源,但創(chuàng)建線程則簡單得多。因此,使用多線程來實現(xiàn)并發(fā)比使用多進程的性能要高得多。

總結(jié)起來,使用多線程編程具有如下幾個優(yōu)點:

  • 進程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。

  • 操作系統(tǒng)在創(chuàng)建進程時,需要為該進程重新分配系統(tǒng)資源,但創(chuàng)建線程的代價則小得多。因此,使用多線程來實現(xiàn)多任務并發(fā)執(zhí)行比使用多進程的效率高。

  • Python 語言內(nèi)置了多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡化了 Python 的多線程編程。

二、線程實現(xiàn)

threading模塊

普通創(chuàng)建方式

          
            import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)
    print('2s')
    time.sleep(1)
    print('1s')
    time.sleep(1)
    print('0s')
    time.sleep(1)

if __name__ == '__main__':
    t1 = threading.Thread(target=run, args=("t1",))
    t2 = threading.Thread(target=run, args=("t2",))
    t1.start()
    t2.start()

----------------------------------

>>> task t1
>>> task t2
>>> 2s
>>> 2s
>>> 1s
>>> 1s
>>> 0s
>>> 0s
          
        

自定義線程

繼承threading.Thread來自定義線程類,其本質(zhì)是重構(gòu)Thread類中的run方法

          
            import threading
import time

class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重構(gòu)run函數(shù)必須要寫
        self.n = n

    def run(self):
        print("task", self.n)
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
        time.sleep(1)
        print('0s')
        time.sleep(1)

if __name__ == "__main__":
    t1 = MyThread("t1")
    t2 = MyThread("t2")
    t1.start()
    t2.start()
    
----------------------------------

>>> task t1
>>> task t2
>>> 2s
>>> 2s
>>> 1s
>>> 1s
>>> 0s
>>> 0s
          
        

守護線程

我們看下面這個例子,這里使用setDaemon(True)把所有的子線程都變成了主線程的守護線程,因此當主進程結(jié)束后,子線程也會隨之結(jié)束。所以當主線程結(jié)束后,整個程序就退出了。

          
            import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)       #此時子線程停1s
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)   #把子進程設置為守護線程,必須在start()之前設置
    t.start()
    print("end")
    
----------------------------------

>>> task t1
>>> end
          
        

我們可以發(fā)現(xiàn),設置守護線程之后,當主線程結(jié)束時,子線程也將立即結(jié)束,不再執(zhí)行。

主線程等待子線程結(jié)束

為了讓守護線程執(zhí)行結(jié)束之后,主線程再結(jié)束,我們可以使用join方法,讓主線程等待子線程執(zhí)行。

          
            import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)       #此時子線程停1s
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)   #把子進程設置為守護線程,必須在start()之前設置
    t.start()
    t.join() # 設置主線程等待子線程結(jié)束
    print("end")

----------------------------------

>>> task t1
>>> 3
>>> 2
>>> 1
>>> end
          
        

多線程共享全局變量

線程是進程的執(zhí)行單元,進程是系統(tǒng)分配資源的最小單位,所以在同一個進程中的多線程是共享資源的。

          
            import threading
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num += 1
    print("in work1 g_num is : %d" % g_num)

def work2():
    global g_num
    print("in work2 g_num is : %d" % g_num)

if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t1.start()
    time.sleep(1)
    t2 = threading.Thread(target=work2)
    t2.start()

----------------------------------

>>> in work1 g_num is : 103
>>> in work2 g_num is : 103
          
        

互斥鎖

由于線程之間是進行隨機調(diào)度,并且每個線程可能只執(zhí)行n條執(zhí)行之后,當多個線程同時修改同一條數(shù)據(jù)時可能會出現(xiàn)臟數(shù)據(jù),所以,出現(xiàn)了線程鎖,即同一時刻允許一個線程執(zhí)行操作。線程鎖用于鎖定資源,你可以定義多個鎖, 像下面的代碼, 當你需要獨占某一資源時,任何一個鎖都可以鎖這個資源,就好比你用不同的鎖都可以把相同的一個門鎖住是一個道理。

由于線程之間是進行隨機調(diào)度,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象,會造成程序結(jié)果的不可預期,我們也稱此為“線程不安全”。

為了方式上面情況的發(fā)生,就出現(xiàn)了互斥鎖(Lock)

          
            from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
          
        

遞歸鎖

RLcok類的用法和Lock類一模一樣,但它支持嵌套,在多個鎖沒有釋放的時候一般會使用RLcok類。

          
            import threading
import time

def Func(lock):
    global gl_num
    lock.acquire()
    gl_num += 1
    time.sleep(1)
    print(gl_num)
    lock.release()

if __name__ == '__main__':
    gl_num = 0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=Func, args=(lock,))
        t.start()
          
        

信號量(BoundedSemaphore類)

互斥鎖同時只允許一個線程更改數(shù)據(jù),而Semaphore是同時允許一定數(shù)量的線程更改數(shù)據(jù) ,比如廁所有3個坑,那最多只允許3個人上廁所,后面的人只能等里面有人出來了才能再進去。

          
            import threading
import time

def run(n, semaphore):
    semaphore.acquire()   #加鎖
    time.sleep(1)
    print("run the thread:%s\n" % n)
    semaphore.release()     #釋放

if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多允許5個線程同時運行
    for i in range(22):
        t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
        t.start()
    while threading.active_count() != 1:
        pass  # print threading.active_count()
    else:
        print('-----all threads done-----')
          
        

事件(Event類)

python線程的事件用于主線程控制其他線程的執(zhí)行,事件是一個簡單的線程同步對象,其主要提供以下幾個方法:

  • clear 將flag設置為“False”
  • set 將flag設置為“True”
  • is_set 判斷是否設置了flag
  • wait 會一直監(jiān)聽flag,如果沒有檢測到flag就一直處于阻塞狀態(tài)

事件處理的機制:全局定義了一個“Flag”,當flag值為“False”,那么event.wait()就會阻塞,當flag值為“True”,那么event.wait()便不再阻塞。

          
            #利用Event類模擬紅綠燈
import threading
import time

event = threading.Event()


def lighter():
    count = 0
    event.set()     #初始值為綠燈
    while True:
        if 5 < count <=10 :
            event.clear()  # 紅燈,清除標志位
            print("\33[41;1mred light is on...\033[0m")
        elif count > 10:
            event.set()  # 綠燈,設置標志位
            count = 0
        else:
            print("\33[42;1mgreen light is on...\033[0m")

        time.sleep(1)
        count += 1

def car(name):
    while True:
        if event.is_set():      #判斷是否設置了標志位
            print("[%s] running..."%name)
            time.sleep(1)
        else:
            print("[%s] sees red light,waiting..."%name)
            event.wait()
            print("[%s] green light is on,start going..."%name)

light = threading.Thread(target=lighter,)
light.start()

car = threading.Thread(target=car,args=("MINI",))
car.start()
          
        

三、GIL(Global Interpreter Lock)全局解釋器鎖

在非python環(huán)境中,單核情況下,同時只能有一個任務執(zhí)行。多核時可以支持多個線程同時執(zhí)行。但是在python中,無論有多少核,同時只能執(zhí)行一個線程。究其原因,這就是由于GIL的存在導致的。

GIL的全稱是Global Interpreter Lock(全局解釋器鎖),來源是python設計之初的考慮,為了數(shù)據(jù)安全所做的決定。某個線程想要執(zhí)行,必須先拿到GIL,我們可以把GIL看作是“通行證”,并且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不允許進入CPU執(zhí)行。GIL只在cpython中才有,因為cpython調(diào)用的是c語言的原生線程,所以他不能直接操作cpu,只能利用GIL保證同一時間只能有一個線程拿到數(shù)據(jù)。而在pypy和jpython中是沒有GIL的。

Python多線程的工作過程:
python在使用多線程的時候,調(diào)用的是c語言的原生線程。

  • 拿到公共數(shù)據(jù)
  • 申請gil
  • python解釋器調(diào)用os原生線程
  • os操作cpu執(zhí)行運算
  • 當該線程執(zhí)行時間到后,無論運算是否已經(jīng)執(zhí)行完,gil都被要求釋放
  • 進而由其他進程重復上面的過程
  • 等其他進程執(zhí)行完后,又會切換到之前的線程(從他記錄的上下文繼續(xù)執(zhí)行),整個過程是每個線程執(zhí)行自己的運算,當執(zhí)行時間到就進行切換(context switch)。

python針對不同類型的代碼執(zhí)行效率也是不同的:

1、CPU密集型代碼(各種循環(huán)處理、計算等等),在這種情況下,由于計算工作多,ticks計數(shù)很快就會達到閾值,然后觸發(fā)GIL的釋放與再競爭(多個線程來回切換當然是需要消耗資源的),所以python下的多線程對CPU密集型代碼并不友好。
2、IO密集型代碼(文件處理、網(wǎng)絡爬蟲等涉及文件讀寫的操作),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執(zhí)行效率)。所以python的多線程對IO密集型代碼比較友好。

使用建議?

python下想要充分利用多核CPU,就用多進程。因為每個進程有各自獨立的GIL,互不干擾,這樣就可以真正意義上的并行執(zhí)行,在python中,多進程的執(zhí)行效率優(yōu)于多線程(僅僅針對多核CPU而言)。

GIL在python中的版本差異:

1、在python2.x里,GIL的釋放邏輯是當前線程遇見IO操作或者ticks計數(shù)達到100時進行釋放。(ticks可以看作是python自身的一個計數(shù)器,專門做用于GIL,每次釋放后歸零,這個計數(shù)可以通過sys.setcheckinterval 來調(diào)整)。而每次釋放GIL鎖,線程進行鎖競爭、切換線程,會消耗資源。并且由于GIL鎖存在,python里一個進程永遠只能同時執(zhí)行一個線程(拿到GIL的線程才能執(zhí)行),這就是為什么在多核CPU上,python的多線程效率并不高。
2、在python3.x中,GIL不使用ticks計數(shù),改為使用計時器(執(zhí)行時間達到閾值后,當前線程釋放GIL),這樣對CPU密集型程序更加友好,但依然沒有解決GIL導致的同一時間只能執(zhí)行一個線程的問題,所以效率依然不盡如人意。


更多文章、技術(shù)交流、商務合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 成人激情视频在线 | 四虎影音| 九九九九精品视频在线播放 | 一二三区在线观看 | 97色伦影院| 人人综合 | 91精品国产91久久久久久麻豆 | 女人a级毛片| 成人99| 日韩欧美理论片 | 亚洲欧美在线看 | 天天操天天摸天天舔 | 色视频网| 国产一级一片免费播放 | 精产国品一二二区视 | free性欧美极度另类超级大 | 精品精品国产高清a毛片牛牛 | 伊人久久99 | 四虎影视库永久在线地址 | 精品久久久久久久久久 | 欧美一级全部免费视频 | 欧美成人aaa大片 | 国产精品ⅴ视频免费观看 | 真实的国产乱xxxx在线播放 | 狠狠亚洲婷婷综合色香 | 国产成人精品一区二区免费视频 | 欧美一级片免费在线观看 | 青青青线在线观看 | 婷婷五月在线视频 | 男女污污视频在线观看 | 久久刺激 | 精品综合久久久久久蜜月 | 香蕉视频免费在线观看 | 玖玖中文 | 悠悠久久| 免费视频99 | 亚洲美女视频网站 | 2021久久精品永久免费 | 久一在线视频 | 黄视频网站免费看 | 一级特级女人18毛片免费视频 |