在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 線程
基礎(chǔ)
itertools
HTTP 服務(wù)
hashlib
閉包
文件和目錄
單元測試
使用 @property
標(biāo)準(zhǔn)模塊
陌生的 metaclass
Base64
進(jìn)程、線程和協(xié)程
讀寫二進(jìn)制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實(shí)例
Python 之旅
定制類和魔法方法
常用數(shù)據(jù)類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達(dá)式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進(jìn)程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級特性
線程
argparse
生成器
結(jié)束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

線程

線程(thread)是進(jìn)程(process)中的一個(gè)實(shí)體,一個(gè)進(jìn)程至少包含一個(gè)線程。比如,對于視頻播放器,顯示視頻用一個(gè)線程,播放音頻用另一個(gè)線程。如果我們把進(jìn)程看成一個(gè)容器,則線程是此容器的工作單位。

進(jìn)程和線程的區(qū)別主要有:

  • 進(jìn)程之間是相互獨(dú)立的,多進(jìn)程中,同一個(gè)變量,各自有一份拷貝存在于每個(gè)進(jìn)程中,但互不影響;而同一個(gè)進(jìn)程的多個(gè)線程是內(nèi)存共享的,所有變量都由所有線程共享;
  • 由于進(jìn)程間是獨(dú)立的,因此一個(gè)進(jìn)程的崩潰不會影響到其他進(jìn)程;而線程是包含在進(jìn)程之內(nèi)的,線程的崩潰就會引發(fā)進(jìn)程的崩潰,繼而導(dǎo)致同一進(jìn)程內(nèi)的其他線程也奔潰;

多線程

在 Python 中,進(jìn)行多線程編程的模塊有兩個(gè):thread 和 threading。其中,thread 是低級模塊,threading 是高級模塊,對 thread 進(jìn)行了封裝,一般來說,我們只需使用 threading 這個(gè)模塊。

下面,我們看一個(gè)簡單的例子:

from threading import Thread, current_thread

def thread_test(name):
    print 'thread %s is running...' % current_thread().name
    print 'hello', name
    print 'thread %s ended.' % current_thread().name

if __name__ == "__main__":
    print 'thread %s is running...' % current_thread().name
    print 'hello world!'
    t = Thread(target=thread_test, args=("test",), name="TestThread")
    t.start()
    t.join()
    print 'thread %s ended.' % current_thread().name

可以看到,創(chuàng)建一個(gè)新的線程,就是把一個(gè)函數(shù)和函數(shù)參數(shù)傳給 Thread 實(shí)例,然后調(diào)用 start 方法開始執(zhí)行。代碼中的 current_thread 用于返回當(dāng)前線程的實(shí)例。

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

thread MainThread is running...
hello world!
thread TestThread is running...
hello test
thread TestThread ended.
thread MainThread ended.

由于同一個(gè)進(jìn)程之間的線程是內(nèi)存共享的,所以當(dāng)多個(gè)線程對同一個(gè)變量進(jìn)行修改的時(shí)候,就會得到意想不到的結(jié)果。

讓我們先看一個(gè)簡單的例子:

from threading import Thread, current_thread

num = 0

def calc():
    global num
    print 'thread %s is running...' % current_thread().name
    for _ in xrange(10000):
        num += 1
    print 'thread %s ended.' % current_thread().name

if __name__ == '__main__':
    print 'thread %s is running...' % current_thread().name

    threads = []
    for i in range(5):
        threads.append(Thread(target=calc))
        threads[i].start()
    for i in range(5):
        threads[i].join()

    print 'global num: %d' % num
    print 'thread %s ended.' % current_thread().name

在上面的代碼中,我們創(chuàng)建了 5 個(gè)線程,每個(gè)線程對全局變量 num 進(jìn)行 10000 次的 加 1 操作,這里之所以要循環(huán) 10000 次,是為了延長單個(gè)線程的執(zhí)行時(shí)間,使線程執(zhí)行時(shí)能出現(xiàn)中斷切換的情況?,F(xiàn)在問題來了,當(dāng)這 5 個(gè)線程執(zhí)行完畢時(shí),全局變量的值是多少呢?是 50000 嗎?

讓我們看下執(zhí)行結(jié)果:

thread MainThread is running...
thread Thread-34 is running...
thread Thread-34 ended.
thread Thread-35 is running...
thread Thread-36 is running...
thread Thread-37 is running...
thread Thread-38 is running...
thread Thread-35 ended.
thread Thread-38 ended.
thread Thread-36 ended.
thread Thread-37 ended.
global num: 30668
thread MainThread ended.

我們發(fā)現(xiàn) num 的值是 30668,事實(shí)上,num 的值是不確定的,你再運(yùn)行一遍,會發(fā)現(xiàn)結(jié)果變了。

原因是因?yàn)?num += 1 不是一個(gè)原子操作,也就是說它在執(zhí)行時(shí)被分成若干步:

  • 計(jì)算 num + 1,存入臨時(shí)變量 tmp 中;
  • 將 tmp 的值賦給 num.

由于線程是交替運(yùn)行的,線程在執(zhí)行時(shí)可能中斷,就會導(dǎo)致其他線程讀到一個(gè)臟值。

為了保證計(jì)算的準(zhǔn)確性,我們就需要給 num += 1 這個(gè)操作加上。當(dāng)某個(gè)線程開始執(zhí)行這個(gè)操作時(shí),由于該線程獲得了鎖,因此其他線程不能同時(shí)執(zhí)行該操作,只能等待,直到鎖被釋放,這樣就可以避免修改的沖突。創(chuàng)建一個(gè)鎖可以通過 threading.Lock() 來實(shí)現(xiàn),代碼如下:

from threading import Thread, current_thread, Lock

num = 0
lock = Lock()

def calc():
    global num
    print 'thread %s is running...' % current_thread().name
    for _ in xrange(10000):
        lock.acquire()    # 獲取鎖
        num += 1
        lock.release()    # 釋放鎖
    print 'thread %s ended.' % current_thread().name

if __name__ == '__main__':
    print 'thread %s is running...' % current_thread().name

    threads = []
    for i in range(5):
        threads.append(Thread(target=calc))
        threads[i].start()
    for i in range(5):
        threads[i].join()

    print 'global num: %d' % num
    print 'thread %s ended.' % current_thread().name

讓我們看下執(zhí)行結(jié)果:

thread MainThread is running...
thread Thread-44 is running...
thread Thread-45 is running...
thread Thread-46 is running...
thread Thread-47 is running...
thread Thread-48 is running...
thread Thread-45 ended.
thread Thread-47 ended.
thread Thread-48 ended.
thread Thread-46 ended.
thread Thread-44 ended.
global num: 50000
thread MainThread ended.

GIL 鎖

講到 Python 中的多線程,就不得不面對 GIL 鎖,GIL 鎖的存在導(dǎo)致 Python 不能有效地使用多線程實(shí)現(xiàn)多核任務(wù),因?yàn)樵谕粫r(shí)間,只能有一個(gè)線程在運(yùn)行。

GIL 全稱是 Global Interpreter Lock,譯為全局解釋鎖。早期的 Python 為了支持多線程,引入了 GIL 鎖,用于解決多線程之間數(shù)據(jù)共享和同步的問題。但這種實(shí)現(xiàn)方式后來被發(fā)現(xiàn)是非常低效的,當(dāng)大家試圖去除 GIL 的時(shí)候,卻發(fā)現(xiàn)大量庫代碼已重度依賴 GIL,由于各種各樣的歷史原因,GIL 鎖就一直保留到現(xiàn)在。

小結(jié)

  • 一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。
  • 進(jìn)程是操作系統(tǒng)分配資源(比如內(nèi)存)的最基本單元,線程是操作系統(tǒng)能夠進(jìn)行調(diào)度和分派的最基本單元。
  • 在 Python 中,進(jìn)行多線程編程的模塊有兩個(gè):thread 和 threading。其中,thread 是低級模塊,threading 是高級模塊,對 thread 進(jìn)行了封裝,一般來說,我們只需使用 threading 這個(gè)模塊。
  • 在執(zhí)行多線程操作時(shí),注意加鎖。

參考資料

下一篇:結(jié)束語