python線程

多任務可以由多進程完成呵哨,也可以由一個進程內的多線程完成。
我們前面提到了進程是由若干線程組成的轨奄,一個進程至少有一個線程孟害。由于線程是操作系統(tǒng)直接支持的執(zhí)行單元,因此挪拟,高級語言通常都內置多線程的支持挨务,Python也不例外,并且玉组,Python的線程是真正的Posix Thread谎柄,而不是模擬出來的線程。
Python的標準庫提供了兩個模塊:_thread和threading惯雳,_thread是低級模塊朝巫,threading是高級模塊,對_thread進行了封裝石景。絕大多數(shù)情況下劈猿,我們只需要使用threading這個高級模塊。
啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例潮孽,然后調用start()開始執(zhí)行
以下是單線程的例子:

import time
import threading


def saySorry():
    print("親愛的揪荣,我錯了,我能吃飯了嗎往史?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        #打印當前線程的名字
        print(threading.current_thread().name)
        saySorry()

結果如下:



這全部都是main線程執(zhí)行的仗颈,運行時間比較慢。接下來采用多線程進行上面的操作椎例。

import threading
import time


def saySorry():
    print(threading.current_thread().name)
    print("親愛的挨决,我錯了请祖,我能吃飯了嗎?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=saySorry)
        t.start()  # 啟動線程凰棉,即讓線程開始執(zhí)行

結果如下:



可以明顯看出使用了多線程并發(fā)的操作损拢,花費時間要短很多
創(chuàng)建好的線程,需要調用start()方法來啟動撒犀。主線程會等待所有的子線程運行結束才會結束福压。

import threading
import time

def sing():
    for i in range(3):
        print("正在唱歌...%d"%i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d"%i)
        time.sleep(1)

if __name__ == '__main__':
    print('---開始---:%s'%time.ctime())

    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)

    t1.start()
    t2.start()

    #time.sleep(5)
    print('---結束---:%s'%time.ctime())

結果如下:



使用threading.enumerate可以查看線程數(shù)量。

import threading
import time

def sing():
    for i in range(3):
        print("正在唱歌...%d"%i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d"%i)
        time.sleep(1)

if __name__ == '__main__':
    print('---開始---:%s'%time.ctime())

    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)

    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        print('當前運行的線程數(shù)為:%d'%length)
        if length<=1:
            break

        time.sleep(0.5)

結果如下:



從這個例子也可以看出主線程會等待所有的子線程運行結束或舞。
根據(jù)我們對進程的學習荆姆,線程同樣可以通過繼承的方法創(chuàng)建。

import threading
import time

class MyThread(threading.Thread):

    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread()
    t.start()

結果如下:



跟進程一樣映凳,可以在創(chuàng)建線程的時候進行參數(shù)的傳遞胆筒。比如修改線程的名字,代碼如下:

import threading
import time

class MyThread(threading.Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread('xx')
    t.start()

結果如下:



python的threading.Thread類有一個run方法诈豌,用于定義線程的功能函數(shù)仆救,可以在自己的線程類中覆蓋該方法。而創(chuàng)建自己的線程實例后矫渔,通過Thread類的start方法彤蔽,可以啟動該線程,交給python虛擬機進行調度庙洼,當該線程獲得執(zhí)行的機會時顿痪,就會調用run方法執(zhí)行線程。
關于線程的執(zhí)行順序油够,看以下代碼:

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i)
            print(msg)
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__ == '__main__':
    test()

結果如下:



從代碼和執(zhí)行結果我們可以看出蚁袭,多線程程序的執(zhí)行順序是不確定的。當執(zhí)行到sleep語句時石咬,線程將被阻塞(Blocked)揩悄,到sleep結束后,線程進入就緒(Runnable)狀態(tài)鬼悠,等待調度虏束。而線程調度將自行選擇一個線程執(zhí)行。上面的代碼中只能保證每個線程都運行完整個run函數(shù)厦章,但是線程的啟動順序镇匀、run函數(shù)中每次循環(huán)的執(zhí)行順序都不能確定。
從以上例子我們可以總結出:每個線程一定會有一個名字袜啃,盡管上面的例子中沒有指定線程對象的name汗侵,但是python會自動為線程指定一個名字。當線程的run()方法結束時該線程完成。無法控制線程調度程序晰韵,但可以通過別的方式來影響線程調度的方式发乔。
線程的幾種狀態(tài)



線程在運行時具有三種基本狀態(tài):
1、執(zhí)行狀態(tài)雪猪,表示線程已獲得處理機而正在運行栏尚;

2、就緒狀態(tài)只恨,指線程已具備了各種執(zhí)行條件译仗,只須再獲得CPU便可立即執(zhí)行;
3官觅、阻塞狀態(tài):指線程在執(zhí)行中因某事件受阻而處于暫停狀態(tài)纵菌,例如,當一個線程執(zhí)行從鍵盤讀入數(shù)據(jù)的系統(tǒng)調用時休涤,該線程就被阻塞咱圆。
在學習進程的時候,我們知道功氨,進程的全局變量是不共享的序苏,每個進程各運行各的,互不影響捷凄,也就是說進程無法修改全局變量忱详。那線程全局變量是否可以共享、修改呢纵势?看如下代碼:

import threading
import time

# 全局變量
g_num = 100


# 修改全局變量
def work1():
    # 如果想修改全局變量踱阿,必須加global
    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)


# 將功能包裝成函數(shù)方法
def main():
    print("---線程創(chuàng)建之前g_num is %d---" % g_num)
    # 創(chuàng)建一個線程
    t1 = threading.Thread(target=work1)
    # 就緒
    t1.start()

    # 延時一會管钳,保證t1線程中的事情做完
    time.sleep(1)

    t2 = threading.Thread(target=work2)
    t2.start()


if __name__ == '__main__':
    # 調用方法
    main()

結果如下:


線程修改全局變量.jpg

多線程和多進程最大的不同在于钦铁,多進程中,同一個變量才漆,各自有一份拷貝存在于每個進程中牛曹,互不影響,而多線程中醇滥,所有變量都由所有線程共享黎比,所以,任何一個變量都可以被任何一個線程修改鸳玩,因此阅虫,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量,把內容給改亂了不跟。來看看多個線程同時操作一個變量怎么把內容給改亂了:

import threading
import time

# 全局變量
g_num = 0


# 函數(shù)1
def test1():
    global  g_num
    for i in range(1000000):
        g_num = g_num+1
    print("---test1---g_num=%d" % g_num)

# 函數(shù)2
def test2():
    global g_num
    for i in range(1000000):
        g_num = g_num + 1
    print("---test2---g_num=%d" % g_num)


def main():
    #創(chuàng)建線程
    t1 = threading.Thread(target=test1)
    #就緒
    t1.start()

    # 創(chuàng)建線程
    t2 = threading.Thread(target=test2)
    # 就緒
    t2.start()


    time.sleep(5)
    print("---main---g_num=%d" % g_num)

if __name__ == '__main__':
    main()

結果如下:

多線程共享全局變量的錯誤.jpg

多線程公用全局變量可能存在的問題假設兩個線程t1和t2都要對g_num=0進行增1運算颓帝,t1和t2都各對g_num修改1000000次,g_num的最終的結果應該為2000000。
為什么會出現(xiàn)這樣的結果购城,原因在于高級語言的一條語句在CPU執(zhí)行時是若干條語句吕座,即使是這么簡單的一個計算:g_num = g_num+1也會分成兩部:
計算g_num+1,存入臨時變量中瘪板;
將臨時變量的值賦給g_num吴趴。
可以看成是:
temp = g_num+1
g_num = temp
由于temp是局部變量,兩個線程各自都有自己的temp侮攀。按理想狀態(tài)下锣枝,代碼是這么運行的:

初始值 g_num = 0

t1: temp = g_num + 1 # temp = 0 + 1 = 1
t1: g_num = temp     # g_num = 1

t2: temp = g_num + 1 # temp = 1 + 1 = 2
t2: g_num = temp     # g_num = 2

就這樣一直循環(huán),最后將得到2000000的結果魏身。
但是t1和t2是交替運行的惊橱,如果操作系統(tǒng)以下面的順序執(zhí)行t1、t2:

初始值 g_num = 0
t1: temp = g_num + 1 # temp = 0 + 1 = 1
t2: temp = g_num + 1 # temp = 0 + 1 = 1
t2: g_num = temp     # g_num = 1
t1: g_num = temp     # g_num = 1

結果就是我們剛才看到的箭昵,這就是由于多線程訪問税朴,有可能出現(xiàn)下面情況:在g_num=0時,t1取得g_num=0家制。此時系統(tǒng)把t1調度為”sleeping”狀態(tài)正林,把t2轉換為”running”狀態(tài),t2也獲得g_num=0颤殴。然后t2對得到的值進行加1并賦給g_num觅廓,使得g_num=1。然后系統(tǒng)又把t2調度為”sleeping”涵但,把t1轉為”running”杈绸。線程t1又把它之前得到的0加1后賦值給g_num。這樣矮瘟,明明t1和t2都完成了1次加1工作瞳脓,但結果仍然是g_num=1。
問題產(chǎn)生的原因就是沒有控制多個線程對同一資源的訪問澈侠,對數(shù)據(jù)造成破壞劫侧,使得線程運行的結果不可預期。這種現(xiàn)象稱為“線程不安全”哨啃。
究其原因烧栋,是因為修改g_num需要多條語句,而執(zhí)行這幾條語句時拳球,線程可能中斷审姓,從而導致多個線程把同一個對象的內容改亂了。所以祝峻,我們必須確保一個線程在修改g_num的時候魔吐,別的線程一定不能改次坡。系統(tǒng)調用t1,然后獲取到num的值為0画畅,此時上一把鎖砸琅,即不允許其他線程操作num,對num的值進行+1轴踱,解鎖症脂。此時num的值為1,其他的線程就可以使用num了淫僻,而且是num的值不是0而是1诱篷。同理其他線程在對num進行修改時,都要先上鎖雳灵,處理完后再解鎖棕所,在上鎖的整個過程中不允許其他線程訪問,就保證了數(shù)據(jù)的正確性悯辙。

import threading

# 全局變量
g_num = 0
myLock = threading.Lock()


# 函數(shù)1
def test1():
    global  g_num
    # 上鎖
    myLock.acquire()
    print('test1...上鎖...')
    for i in range(1000000):
        g_num = g_num+1
    # 開鎖
    myLock.release()
    print('test1...開鎖...')
    print("---test1---g_num=%d" % g_num)

# 函數(shù)2
def test2():
    print('test2...')
    '''
    print('g_num:%s'%g_num)
    如使用這句話將報錯SyntaxWarning: name 'g_num' is used
    prior to global declaration global g_num
    '''
    global g_num
    myLock.acquire()
    print('test2...上鎖...')
    print('g_num:%s'%g_num)
    for i in range(1000000):
        g_num = g_num + 1
        # 開鎖
    myLock.release()
    print('test2...開鎖...')
    print("---test2---g_num=%d" % g_num)


def main():
    #創(chuàng)建線程
    t1 = threading.Thread(target=test1)
    #就緒
    t1.start()


    # 創(chuàng)建線程
    t2 = threading.Thread(target=test2)
    # 就緒
    t2.start()
    print("---main---g_num=%d" % g_num)

if __name__ == '__main__':
    main()

結果如下:


線程上鎖.jpg

上鎖是為了保護線程訪問數(shù)據(jù)的唯一性琳省,并不妨礙上鎖后,cpu對線程的切換躲撰,當上鎖的代碼段執(zhí)行過程中针贬,cpu切換走,其他線程如果是上鎖等待拢蛋,則cpu繼續(xù)切換桦他,如果cpu切換的其他線程不是上鎖等待,則其他線程依然可執(zhí)行谆棱。cpu的線程切換是不固定的快压。并不是一定要等待上鎖的程序走完。這也就解釋了為什么打印test1...上鎖...后又打印了test2...垃瞧。
當一個線程調用鎖的acquire()方法獲得鎖時蔫劣,鎖就進入“l(fā)ocked”狀態(tài)。每次只有一個線程可以獲得鎖皆警。如果此時另一個線程試圖獲得這個鎖拦宣,該線程就會變?yōu)椤癰locked”狀態(tài)截粗,稱為“阻塞”信姓,直到擁有鎖的線程調用鎖的release()方法釋放鎖之后,鎖進入“unlocked”狀態(tài)绸罗。
鎖的好處:
確保了某段關鍵代碼只能由一個線程從頭到尾完整地執(zhí)行
鎖的壞處:
阻止了多線程并發(fā)執(zhí)行意推,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了珊蟀。由于可以存在多個鎖菊值,不同的線程持有不同的鎖外驱,并試圖獲取對方持有的鎖時,可能會造成死鎖腻窒。
從剛才的例子中可以看出對于全局變量昵宇,在多線程中要格外小心,否則容易造成數(shù)據(jù)錯亂的情況發(fā)生儿子。那對于非全局變量是否需要加鎖呢瓦哎?看如下代碼:

import threading
import time


class MyThread(threading.Thread):
    # 重寫 構造方法
    def __init__(self, num, sleepTime):
        # 重寫父類的__init__()方法
        threading.Thread.__init__(self)
        self.num = num
        self.sleepTime = sleepTime

    def run(self):
        self.num += 1
        time.sleep(self.sleepTime)
        print('線程(%s),num=%d' % (self.name, self.num))


if __name__ == '__main__':
    mutex = threading.Lock()
    t1 = MyThread(100, 5)
    t1.start()
    t2 = MyThread(200, 1)
    t2.start()

結果如下:


多線程局部變量.jpg

再觀察如下代碼:

import threading
import time


def test(sleepTime):
    num = 1
    time.sleep(sleepTime)
    num += 1
    print('---(%s)--num=%d' % (threading.current_thread(), num))


t1 = threading.Thread(target=test, args=(5,))
t2 = threading.Thread(target=test, args=(1,))

t1.start()
t2.start()

結果如下:


多線程局部變量2.jpg

也就是說在多線程開發(fā)中,全局變量是多個線程都共享的數(shù)據(jù)柔逼,而局部變量等是各自線程的蒋譬,是非共享的。
在剛才講鎖的時候說到了死鎖愉适,那么什么是死鎖呢犯助?在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源并且同時等待對方的資源维咸,就會造成死鎖剂买。盡管死鎖很少發(fā)生,但一旦發(fā)生就會造成應用的停止響應癌蓖。下面看一個死鎖的例子:

import threading
import time


class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name + '----do1---up----')
            time.sleep(1)

            if mutexB.acquire():
                print(self.name + '----do1---down----')
                mutexB.release()
            mutexA.release()


class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name + '----do2---up----')
            time.sleep(1)
            if mutexA.acquire():
                print(self.name + '----do2---down----')
                mutexA.release()
            mutexB.release()


mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

結果如下:


死鎖.jpg

死鎖原理.jpg

那我們如何避免死鎖呢雷恃?常用的是在程序設計時要盡量避免(比如采用銀行家算法),要么添加超時時間等费坊。
有了以上的知識倒槐,就可以讓線程按我們的需求的順序執(zhí)行:

import threading
import time


class Task1(threading.Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print("------Task 1 -----")
                time.sleep(0.5)
                lock2.release()


class Task2(threading.Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print("------Task 2 -----")
                time.sleep(0.5)
                lock3.release()


class Task3(threading.Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print("------Task 3 -----")
                time.sleep(0.5)
                lock1.release()


# 使用Lock創(chuàng)建出的鎖默認沒有“鎖上”
lock1 = threading.Lock()
# 創(chuàng)建另外一把鎖,并且“鎖上”
lock2 = threading.Lock()
lock2.acquire()
# 創(chuàng)建另外一把鎖附井,并且“鎖上”
lock3 = threading.Lock()
lock3.acquire()

t1 = Task1()
t2 = Task2()
t3 = Task3()

t1.start()
t2.start()
t3.start()

結果如下:


進程同步應用.jpg

也就是說我們可以可以使用互斥鎖完成多個任務讨越,有序的進程工作,這就是線程的同步永毅。接下來把跨,我們模擬一下車站的賣票:

import threading
import time
import os


def doChore():  # 作為間隔  每次調用間隔0.5s
    time.sleep(0.5)


def booth(tid):
    global i
    global lock
    while True:
        lock.acquire()  # 得到一個鎖,鎖定
        if i != 0:
            i = i - 1  # 售票 售出一張減少一張
            print(tid, ':now left:', i)  # 剩下的票數(shù)
            doChore()
        else:
            print("Thread_id", tid, " No more tickets")
            os._exit(0)  # 票售完   退出程序
        lock.release()  # 釋放鎖
        doChore()


# 全局變量
i = 15  # 初始化票數(shù)
lock = threading.Lock()  # 創(chuàng)建鎖


def main():
    # 總共設置了3個線程
    for k in range(3):
        # 創(chuàng)建線程; Python使用threading.Thread對象來代表線程
        new_thread = threading.Thread(target=booth, args=(k,))
        # 調用start()方法啟動線程
        new_thread.start()


if __name__ == '__main__':
    main()

結果如下:


模擬賣票.jpg

以上就是關于線程同步的總結沼死。
Python的Queue模塊中提供了同步的着逐、線程安全的隊列類,包括FIFO(先入先出)隊列Queue意蛀,LIFO(后入先出)隊列LifoQueue耸别,和優(yōu)先級隊列PriorityQueue。這些隊列都實現(xiàn)了鎖原語(可以理解為原子操作县钥,即要么不做秀姐,要么就做完),能夠在多線程中直接使用若贮∈∮校可以使用隊列來實現(xiàn)線程間的同步痒留。
用FIFO隊列實現(xiàn)上述生產(chǎn)者與消費者問題的代碼如下:

import threading
import time
import queue


class Producer(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            if queue.qsize() < 1000:
                for i in range(100):
                    count = count + 1
                    msg = '生成產(chǎn)品' + str(count)
                    queue.put(msg)
                    print(msg)
            time.sleep(0.5)


class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name + '消費了 ' + queue.get()
                    print(msg)
            time.sleep(1)


if __name__ == '__main__':
    queue = queue.Queue()

    for i in range(500):
        queue.put('初始產(chǎn)品' + str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()

結果如下:


生產(chǎn)者消費者.jpg

為什么要使用生產(chǎn)者和消費者模式?
在線程世界里蠢沿,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程伸头,消費者就是消費數(shù)據(jù)的線程。在多線程開發(fā)當中舷蟀,如果生產(chǎn)者處理速度很快熊锭,而消費者處理速度很慢,那么生產(chǎn)者就必須等待消費者處理完雪侥,才能繼續(xù)生產(chǎn)數(shù)據(jù)碗殷。同樣的道理,如果消費者的處理能力大于生產(chǎn)者速缨,那么消費者就必須等待生產(chǎn)者锌妻。為了解決這個問題于是引入了生產(chǎn)者和消費者模式。
什么是生產(chǎn)者消費者模式旬牲?
生產(chǎn)者消費者模式是通過一個容器來解決生產(chǎn)者和消費者的強耦合問題仿粹。生產(chǎn)者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊原茅,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費者處理吭历,直接扔給阻塞隊列,消費者不找生產(chǎn)者要數(shù)據(jù)擂橘,而是直接從阻塞隊列里取晌区,阻塞隊列就相當于一個緩沖區(qū),平衡了生產(chǎn)者和消費者的處理能力通贞。
這個阻塞隊列就是用來給生產(chǎn)者和消費者解耦的朗若。縱觀大多數(shù)設計模式昌罩,都會找一個第三者出來進行解耦哭懈。

  1. Queue的說明
    對于Queue,在多線程通信之間扮演重要的角色
    1茎用、添加數(shù)據(jù)到隊列中遣总,使用put()方法
    2、從隊列中取數(shù)據(jù)轨功,使用get()方法
    3旭斥、判斷隊列中是否還有數(shù)據(jù),使用qsize()方法
    在多線程中夯辖,如果使用全局變量琉预,會出問題董饰,一旦一個線程修改了蒿褂,會影響其它線程圆米。如果使用局部變量,一旦想讓這個局部變量啄栓,一直使用下去娄帖,一直傳參,比較麻煩昙楚。既不想使用全局變量近速,又一直想用這個局部變量】熬桑可以將這個局部變量綁定到對應的線程中削葱,以后此線程一直用個。與其它線程中的無關淳梦。
    嘗試用一個全局dict存放所有的Student對象析砸,然后以thread自身作為key獲得線程對應的Student對象:
import threading
# 創(chuàng)建字典對象
myDict = {}
def process_student():
    # 獲取當前線程關聯(lián)的student
    std = myDict[threading.current_thread()]
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
    # 綁定ThreadLocal的student
    myDict[threading.current_thread()] = name
    process_student()
t1 = threading.Thread(target=process_thread, args=('yongGe',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()

結果如下:


局部變量傳參.jpg

這種方式理論上是可行的,它最大的優(yōu)點是消除了std對象在每層函數(shù)中的傳遞問題爆袍,但是首繁,每個函數(shù)獲取std的代碼有點low≡赡遥看下面的代碼:

import threading
# 創(chuàng)建全局ThreadLocal對象
local_school = threading.local()
def process_student():
    # 獲取當前線程關聯(lián)的student
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
    # 綁定ThreadLocal的student
    local_school.student = name
    process_student()
t1 = threading.Thread(target=process_thread, args=('yongGe',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()

結果如下:


局部變量傳參local.jpg

全局變量local_school就是一個ThreadLocal對象弦疮,每個Thread對它都可以讀寫student屬性,但互不影響蜘醋。你可以把local_school看成全局變量胁塞,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾压语,也不用管理鎖的問題闲先,ThreadLocal內部會處理∥薹洌可以理解為全局變量local_school是一個dict伺糠,不但可以用local_school.student,還可以綁定其他變量斥季,如local_school.teacher等等训桶。ThreadLocal最常用的地方就是為每個線程綁定一個數(shù)據(jù)庫連接,HTTP請求酣倾,用戶身份信息等舵揭,這樣一個線程的所有調用到的處理函數(shù)都可以非常方便地訪問這些資源。一個ThreadLocal變量雖然是全局變量躁锡,但每個線程都只能讀寫自己線程的獨立副本午绳,互不干擾。ThreadLocal解決了參數(shù)在一個線程中各個函數(shù)之間互相傳遞的問題映之。
異步

from multiprocessing import Pool
import time
import os
def test():
    print("---進程池中的進程---pid=%d,ppid=%d--" % (os.getpid(), os.getppid()))
    for i in range(3):
        print("----%d---" % i)
    return "老王"
def test2(args):
    print('1...')
    print("---callback func--pid=%d" % os.getpid())
    print("---callback func--args=%s" % args)
    print('2...')
if __name__ == '__main__':
    pool = Pool(3)
    # callback表示前面的func方法執(zhí)行完拦焚,再執(zhí)行callback,并且可以獲取func的返回值作為callback的參數(shù)
    pool.apply_async(func=test, callback=test2)
    # pool.apply_async(func=test)

    # 模擬主進程在做任務
    time.sleep(5)

    print("----主進程-pid=%d.....----" % os.getpid())

結果如下:

異步1.jpg

可以看到執(zhí)行callback的是主進程蜡坊。也就是說當主進程在做任務時,進程又分出了一個線程去做其他任務赎败。
多線程編程秕衙,模型復雜,容易發(fā)生沖突僵刮,必須用鎖加以隔離据忘,同時,又要小心死鎖的發(fā)生搞糕。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末勇吊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窍仰,更是在濱河造成了極大的恐慌萧福,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辈赋,死亡現(xiàn)場離奇詭異鲫忍,居然都是意外死亡,警方通過查閱死者的電腦和手機钥屈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門悟民,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篷就,你說我怎么就攤上這事射亏。” “怎么了竭业?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵智润,是天一觀的道長。 經(jīng)常有香客問我未辆,道長窟绷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任咐柜,我火速辦了婚禮兼蜈,結果婚禮上,老公的妹妹穿的比我還像新娘拙友。我一直安慰自己为狸,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布遗契。 她就那樣靜靜地躺著辐棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漾根,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天泰涂,我揣著相機與錄音,去河邊找鬼立叛。 笑死负敏,一個胖子當著我的面吹牛贡茅,可吹牛的內容都是我干的秘蛇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顶考,長吁一口氣:“原來是場噩夢啊……” “哼赁还!你這毒婦竟也來了?” 一聲冷哼從身側響起驹沿,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤艘策,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渊季,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朋蔫,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年却汉,在試婚紗的時候發(fā)現(xiàn)自己被綠了驯妄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡合砂,死狀恐怖青扔,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情翩伪,我是刑警寧澤微猖,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站缘屹,受9級特大地震影響凛剥,放射性物質發(fā)生泄漏。R本人自食惡果不足惜轻姿,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一当悔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踢代,春花似錦盲憎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春窑眯,著一層夾襖步出監(jiān)牢的瞬間屏积,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工磅甩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炊林,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓卷要,卻偏偏與公主長得像渣聚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • 1.進程和線程 隊列:1、進程之間的通信: q = multiprocessing.Queue()2匠题、...
    一只寫程序的猿閱讀 1,109評論 0 17
  • 線程狀態(tài)新建图仓,就緒,運行,阻塞,死亡。 線程同步多線程可以同時運行多個任務谭梗,線程需要共享數(shù)據(jù)的時候,可能出現(xiàn)數(shù)據(jù)不...
    KevinCool閱讀 806評論 0 0
  • 線程 1.同步概念 1.多線程開發(fā)可能遇到的問題 同步不是一起的意思宛蚓,是協(xié)同步調 假設兩個線程t1和t2都要對nu...
    TENG書閱讀 611評論 0 1
  • 1.線程 Python中使用線程有兩種方式:函數(shù)或者用類來包裝線程對象激捏。 1.函數(shù)式:調用thread模塊中的st...
    一只寫程序的猿閱讀 1,000評論 0 1
  • 十幾年前春梅的老公晚上在家剛剛洗完腳,做沙發(fā)上就再也沒起來苍息,心梗缩幸!料理后事的時候,發(fā)現(xiàn)他身上有一串鑰匙竞思,也不是家...
    藍夜伊夢閱讀 209評論 0 0