亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

在Python下嘗試多線程編程

 更新時間:2015年04月28日 15:28:58   作者:廖雪峰  
這篇文章主要介紹了在Python下多線程編程的嘗試,由于GIL的存在,多線程在Python開發(fā)領(lǐng)域一直是個熱門問題,需要的朋友可以參考下

多任務可以由多進程完成,也可以由一個進程內(nèi)的多線程完成。

我們前面提到了進程是由若干線程組成的,一個進程至少有一個線程。

由于線程是操作系統(tǒng)直接支持的執(zhí)行單元,因此,高級語言通常都內(nèi)置多線程的支持,Python也不例外,并且,Python的線程是真正的Posix Thread,而不是模擬出來的線程。

Python的標準庫提供了兩個模塊:thread和threading,thread是低級模塊,threading是高級模塊,對thread進行了封裝。絕大多數(shù)情況下,我們只需要使用threading這個高級模塊。

啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例,然后調(diào)用start()開始執(zhí)行:

import time, threading

# 新線程執(zhí)行的代碼:
def loop():
  print 'thread %s is running...' % threading.current_thread().name
  n = 0
  while n < 5:
    n = n + 1
    print 'thread %s >>> %s' % (threading.current_thread().name, n)
    time.sleep(1)
  print 'thread %s ended.' % threading.current_thread().name

print 'thread %s is running...' % threading.current_thread().name
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print 'thread %s ended.' % threading.current_thread().name

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

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

由于任何進程默認就會啟動一個線程,我們把該線程稱為主線程,主線程又可以啟動新的線程,Python的threading模塊有個current_thread()函數(shù),它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創(chuàng)建時指定,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給線程命名為Thread-1,Thread-2……
Lock

多線程和多進程最大的不同在于,多進程中,同一個變量,各自有一份拷貝存在于每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量,把內(nèi)容給改亂了。

來看看多個線程同時操作一個變量怎么把內(nèi)容給改亂了:

import time, threading

 
# 假定這是你的銀行存款:
balance = 0

def change_it(n):
  # 先存后取,結(jié)果應該為0:
  global balance
  balance = balance + n
  balance = balance - n

def run_thread(n):
  for i in range(100000):
    change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print balance

我們定義了一個共享變量balance,初始值為0,并且啟動兩個線程,先存后取,理論上結(jié)果應該為0,但是,由于線程的調(diào)度是由操作系統(tǒng)決定的,當t1、t2交替執(zhí)行時,只要循環(huán)次數(shù)足夠多,balance的結(jié)果就不一定是0了。

原因是因為高級語言的一條語句在CPU執(zhí)行時是若干條語句,即使一個簡單的計算:

balance = balance + n

也分兩步:

  1.     計算balance + n,存入臨時變量中;
  2.     將臨時變量的值賦給balance。

也就是可以看成:

x = balance + n
balance = x

由于x是局部變量,兩個線程各自都有自己的x,當代碼正常執(zhí)行時:

初始值 balance = 0

t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1   # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1   # balance = 0

t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2   # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2   # balance = 0

結(jié)果 balance = 0

但是t1和t2是交替運行的,如果操作系統(tǒng)以下面的順序執(zhí)行t1、t2:

初始值 balance = 0

t1: x1 = balance + 5 # x1 = 0 + 5 = 5

t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2   # balance = 8

t1: balance = x1   # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1   # balance = 0

t2: x2 = balance - 5 # x2 = 0 - 5 = -5
t2: balance = x2   # balance = -5

結(jié)果 balance = -5

究其原因,是因為修改balance需要多條語句,而執(zhí)行這幾條語句時,線程可能中斷,從而導致多個線程把同一個對象的內(nèi)容改亂了。

兩個線程同時一存一取,就可能導致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負數(shù),所以,我們必須確保一個線程在修改balance的時候,別的線程一定不能改。

如果我們要確保balance計算正確,就要給change_it()上一把鎖,當某個線程開始執(zhí)行change_it()時,我們說,該線程因為獲得了鎖,因此其他線程不能同時執(zhí)行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由于鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,所以,不會造成修改的沖突。創(chuàng)建一個鎖就是通過threading.Lock()來實現(xiàn):

balance = 0
lock = threading.Lock()

def run_thread(n):
  for i in range(100000):
    # 先要獲取鎖:
    lock.acquire()
    try:
      # 放心地改吧:
      change_it(n)
    finally:
      # 改完了一定要釋放鎖:
      lock.release()

當多個線程同時執(zhí)行l(wèi)ock.acquire()時,只有一個線程能成功地獲取鎖,然后繼續(xù)執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖為止。

獲得鎖的線程用完后一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠等待下去,成為死線程。所以我們用try...finally來確保鎖一定會被釋放。

鎖的好處就是確保了某段關(guān)鍵代碼只能由一個線程從頭到尾完整地執(zhí)行,壞處當然也很多,首先是阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了。其次,由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能會造成死鎖,導致多個線程全部掛起,既不能執(zhí)行,也無法結(jié)束,只能靠操作系統(tǒng)強制終止。
多核CPU

如果你不幸擁有一個多核CPU,你肯定在想,多核應該可以同時執(zhí)行多個線程。

如果寫一個死循環(huán)的話,會出現(xiàn)什么情況呢?

打開Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以監(jiān)控某個進程的CPU使用率。

我們可以監(jiān)控到一個死循環(huán)線程會100%占用一個CPU。

如果有兩個死循環(huán)線程,在多核CPU中,可以監(jiān)控到會占用200%的CPU,也就是占用兩個CPU核心。

要想把N核CPU的核心全部跑滿,就必須啟動N個死循環(huán)線程。

試試用Python寫個死循環(huán):

import threading, multiprocessing

def loop():
  x = 0
  while True:
    x = x ^ 1

for i in range(multiprocessing.cpu_count()):
  t = threading.Thread(target=loop)
  t.start()

啟動與CPU核心數(shù)量相同的N個線程,在4核CPU上可以監(jiān)控到CPU占用率僅有160%,也就是使用不到兩核。

即使啟動100個線程,使用率也就170%左右,仍然不到兩核。

但是用C、C++或Java來改寫相同的死循環(huán),直接可以把全部核心跑滿,4核就跑到400%,8核就跑到800%,為什么Python不行呢?

因為Python的線程雖然是真正的線程,但解釋器執(zhí)行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執(zhí)行前,必須先獲得GIL鎖,然后,每執(zhí)行100條字節(jié)碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執(zhí)行。這個GIL全局鎖實際上把所有線程的執(zhí)行代碼都給上了鎖,所以,多線程在Python中只能交替執(zhí)行,即使100個線程跑在100核CPU上,也只能用到1個核。

GIL是Python解釋器設計的歷史遺留問題,通常我們用的解釋器是官方實現(xiàn)的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。

所以,在Python中,可以使用多線程,但不要指望能有效利用多核。如果一定要通過多線程利用多核,那只能通過C擴展來實現(xiàn),不過這樣就失去了Python簡單易用的特點。

不過,也不用過于擔心,Python雖然不能利用多線程實現(xiàn)多核任務,但可以通過多進程實現(xiàn)多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。
小結(jié)

多線程編程,模型復雜,容易發(fā)生沖突,必須用鎖加以隔離,同時,又要小心死鎖的發(fā)生。

Python解釋器由于設計時有GIL全局鎖,導致了多線程無法利用多核。多線程的并發(fā)在Python中就是一個美麗的夢。

 

相關(guān)文章

  • Python利用sched模塊實現(xiàn)定時任務

    Python利用sched模塊實現(xiàn)定時任務

    今天我們來介紹一下Python當中的定時任務,主要用到的模塊是sched,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-04-04
  • Python統(tǒng)計日志中每個IP出現(xiàn)次數(shù)的方法

    Python統(tǒng)計日志中每個IP出現(xiàn)次數(shù)的方法

    這篇文章主要介紹了Python統(tǒng)計日志中每個IP出現(xiàn)次數(shù)的方法,實例分析了Python基于正則表達式解析日志文件的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07
  • Python實現(xiàn)環(huán)形鏈表

    Python實現(xiàn)環(huán)形鏈表

    這篇文章主要為大家詳細介紹了Python實現(xiàn)環(huán)形鏈表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • django傳值給模板, 再用JS接收并進行操作的實例

    django傳值給模板, 再用JS接收并進行操作的實例

    今天小編就為大家分享一篇django傳值給模板, 再用JS接收并進行操作的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • python中類的相互調(diào)用的實踐

    python中類的相互調(diào)用的實踐

    本文主要介紹了python中類的相互調(diào)用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • python單元測試之pytest的使用

    python單元測試之pytest的使用

    Pytest是Python的一種單元測試框架,與 Python 自帶的 Unittest 測試框架類似,但是比 Unittest 框架使用起來更簡潔,效率更高,今天給大家詳細介紹一下pytest的使用,需要的朋友可以參考下
    2021-06-06
  • Python中os模塊的使用及文件對象的讀寫詳解

    Python中os模塊的使用及文件對象的讀寫詳解

    這篇文章主要介紹了Python中os模塊的使用及文件對象的讀寫詳解,Python?open()?方法用于打開一個文件,并創(chuàng)建返回文件對象,在對文件進行處理過程都需要使用到這個函數(shù),如果該文件無法被打開,會拋出?OSError,需要的朋友可以參考下
    2023-08-08
  • django admin后管定制-顯示字段的實例

    django admin后管定制-顯示字段的實例

    這篇文章主要介紹了django admin后管定制-顯示字段的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03
  • Python數(shù)據(jù)庫小程序源代碼

    Python數(shù)據(jù)庫小程序源代碼

    這篇文章主要介紹了Python數(shù)據(jù)庫小程序源代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • Python 獲取主機ip與hostname的方法

    Python 獲取主機ip與hostname的方法

    今天小編就為大家分享一篇Python 獲取主機ip與hostname的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-12-12

最新評論