天天看點

Python系統程式設計--線程1.多線程-threading2. threading注意點3. 多線程-共享全局變量4. 程序VS線程5. 同步的概念6. 互斥鎖7. 死鎖8. 多線程-非共享資料9. 同步應用10. 生産者與消費者模式11.ThreadLocal12. 異步

1.多線程-threading

python的thread子產品是比較底層的子產品,python的threading子產品是對thread做了一些包裝的,可以更加友善的被使用

1.1 threading子產品的使用

示例:單線程執行

import time

def loveStudy():
    print("我愛學習,學習使我快樂")
    time.sleep()

if __name__ == "__main__":
    for i in range():
        loveStudy()
           

運作結果:間隔0.5s,逐個打出

我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
           

示例:多線程執行

import threading
import time

def loveStudy():
    print("我愛學習,學習使我快樂")
    time.sleep()

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

運作結果:結果幾乎同時打出

我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
我愛學習,學習使我快樂
           

說明:

1. 可以明顯看出使用了多線程并發的操作,花費時間要短很多

2. 建立好的線程,需要調用start()方法來啟動

1.2 主線程會等待所有的子線程結束後才結束

示例:

import threading
from time import sleep,ctime

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

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

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

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

    t1.start()
    t2.start()

    sleep() # 屏蔽此行代碼,試試看,程式是否會立馬結束?
    print('---結束---:%s'%ctime())
           

運作結果:

---開始---:Mon Jun 12 21:24:35 2017
正在唱歌...0
正在跳舞...0
正在跳舞...1
正在唱歌...1
正在唱歌...2
正在跳舞...2
---結束---:Mon Jun 12 21:24:40 2017
           

1.3 檢視線程數量

示例:

import threading
from time import sleep,ctime

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

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

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

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

    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        print('目前運作的線程數為:%d'%length)
        if length<=:
            break

        sleep()
           

運作結果:

---開始---:Mon Jun  :: 
正在唱歌...
正在跳舞...
目前運作的線程數為:
目前運作的線程數為:
正在唱歌...
目前運作的線程數為:
正在跳舞...
目前運作的線程數為:
正在唱歌...
正在跳舞...
目前運作的線程數為:
目前運作的線程數為:
目前運作的線程數為:
目前運作的線程數為:
           

2. threading注意點

2.1 線程執行代碼的封裝

通過上一小節,能夠看出,通過使用threading子產品能完成多任務的程式開發,為了讓每個線程的封裝性更完美,是以使用threading子產品時,往往會定義一個新的子類class,隻要繼承threading.Thread就可以了,然後重寫run方法

示例:

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range():
            time.sleep()
            msg = "I'm "+self.name+' @ '+str(i) #name屬性中儲存的是目前線程的名字
            print(msg)


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

運作結果:

I'm Thread-1 @ 0
I'm Thread-1 @ 1
I'm Thread-1 @ 2
           

說明:

• python的threading.Thread類有一個run方法,用于定義線程的功能函數,可以在自己的線程類中覆寫該方法。而建立自己的線程執行個體後,通過Thread類的start方法,可以啟動該線程,交給python虛拟機進行排程,當該線程獲得執行的機會時,就會調用run方法執行線程。

2.2 線程的執行順序

示例:

import threading
import time

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

運作結果:

I'm Thread-1 @ 0
I'm Thread-3 @ 0
I'm Thread-4 @ 0
I'm Thread-2 @ 0
I'm Thread-5 @ 0
I'm Thread-1 @ 1
I'm Thread-3 @ 1
I'm Thread-4 @ 1
I'm Thread-2 @ 1
I'm Thread-5 @ 1
I'm Thread-1 @ 2
I'm Thread-3 @ 2
I'm Thread-4 @ 2
I'm Thread-2 @ 2
I'm Thread-5 @ 2
           

說明:

從代碼和執行結果我們可以看出,多線程程式的執行順序是不确定的。當執行到sleep語句時,線程将被阻塞(Blocked),到sleep結束後,線程進入就緒(Runnable)狀态,等待排程。而線程排程将自行選擇一個線程執行。上面的代碼中隻能保證每個線程都運作完整個run函數,但是線程的啟動順序、run函數中每次循環的執行順序都不能确定。

總結:

1. 每個線程一定會有一個名字,盡管上面的例子中沒有指定線程對象的name,但是python會自動為線程指定一個名字。

2. 當線程的run()方法結束時該線程完成。

3. 無法控制線程排程程式,但可以通過别的方式來影響線程排程的方式。

4. 線程的幾種狀态:

Python系統程式設計--線程1.多線程-threading2. threading注意點3. 多線程-共享全局變量4. 程式VS線程5. 同步的概念6. 互斥鎖7. 死鎖8. 多線程-非共享資料9. 同步應用10. 生産者與消費者模式11.ThreadLocal12. 異步

3. 多線程-共享全局變量

示例:

from threading import Thread
import time

g_num = 

#修改全局變量
def work1():
    #如果想修改全局變量,必須加global
    global g_num
    for i in range():
        g_num += 
    print("----in work1, g_num is %d---"%g_num)

#擷取全局變量
def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)

#将功能包裝成函數方法
def main():
    print("---線程建立之前g_num is %d---" % g_num)
    #建立一個線程
    t1 = Thread(target=work1)
    #就緒
    t1.start()

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

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

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

運作結果:

---線程建立之前g_num is ---
----in work1, g_num is ---
----in work2, g_num is ---
           

總結:

• 在一個程序内的所有線程共享全局變量,能夠在不适用其他方式的前提下完成多線程之間的資料共享(這點要比多程序要好)

• 缺點就是,線程是對全局變量随意遂改可能造成多線程之間對全局變量的混亂(即線程非安全)

4. 程序VS線程

4.1 功能差別

• 程序,能夠完成多任務,比如 在一台電腦上能夠同時運作多個QQ

• 線程,能夠完成多任務,比如 一個QQ中的多個聊天視窗

4.2 定義差別

• 程序是系統進行資源配置設定和排程的一個獨立機關.

• 線程是程序的一個實體,是CPU排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關.線程自己基本上不擁有系統資源,隻擁有一點在運作中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可與同屬一個程序的其他的線程共享程序所擁有的全部資源.

4.3 差別

• 一個程式至少有一個程序,一個程序至少有一個線程.

• 線程的劃分尺度小于程序(資源比程序少),使得多線程程式的并發性高。

• 程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體,進而極大地提高了程式的運作效率

• 線程不能夠獨立執行,必須依存在程序中

4.4 優缺點

線程和程序在使用上各有優缺點:線程執行開銷小,但不利于資源的管理和保護;而程序正相反。
           

5. 同步的概念

5.1 多線程開發可能遇到的問題

同步不是一起的意思,是協同步調

假設兩個線程t1和t2都要對num=0進行增1運算,t1和t2都各對num修改10次,num的最終的結果應該為20。

但是由于是多線程通路,有可能出現下面情況:

在num=0時,t1取得num=0。此時系統把t1排程為”sleeping”狀态,把t2轉換為”running”狀态,t2也獲得num=0。然後t2對得到的值進行加1并賦給num,使得num=1。然後系統又把t2排程為”sleeping”,把t1轉為”running”。線程t1又把它之前得到的0加1後指派給num。這樣,明明t1和t2都完成了1次加1工作,但結果仍然是num=1。

示例:

import threading
import time

# 全局變量
g_num = 


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

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


def main():
    #建立線程
    t1 = threading.Thread(target=test1)
    #就緒
    t1.start()

    # 建立線程
    t2 = threading.Thread(target=test2)
    # 就緒
    t2.start()


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

if __name__ == '__main__':
    main()



def main():
    #建立線程
    t1 = threading.Thread(target=test1)
    #就緒
    t1.start()

    # 建立線程
    t2 = threading.Thread(target=test2)
    # 就緒
    t2.start()


    time.sleep() #屏蔽此行代碼又是一個結果
    print("---main---g_num=%d" % g_num)

if __name__ == '__main__':
    main()
           

運作結果:

---test1---g_num=1135793
---test2---g_num=1134965
---main---g_num=1134965
           

屏蔽後運作結果:

---main---g_num=201214
---test1---g_num=1139845
---test2---g_num=1157151
           

問題産生的原因就是沒有控制多個線程對同一資源的通路,對資料造成破壞,使得線程運作的結果不可預期。這種現象稱為“線程不安全”。

5.2 同步

同步就是協同步調,按預定的先後次序進行運作。如:你說完,我再說。

“同”字從字面上容易了解為一起動作

其實不是,”同”字應是指協同、協助、互相配合。

如程序、線程同步,可了解為程序或線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,于是停下來,示意B運作;B依言執行,再将結果給A;A再繼續操作。

5.3 解決問題的思路

對于本小節提出的那個計算錯誤的問題,可以通過線程同步來進行解決思路,如下:

1. 系統調用t1,然後擷取到num的值為0,此時上一把鎖,即不允許其他現在操作num

2. 對num的值進行+1

3. 解鎖,此時num的值為1,其他的線程就可以使用num了,而且是num的值不是0而是1

4. 同理其他線程在對num進行修改時,都要先上鎖,處理完後再解鎖,在上鎖的整個過程中不允許其他線程通路,就保證了資料的正确性

6. 互斥鎖

定義:

當多個線程幾乎同時修改某一個共享資料的時候,需要進行同步控制

線程同步能夠保證多個線程安全通路競争資源,最簡單的同步機制是引入互斥鎖。

互斥鎖為資源引入一個狀态:鎖定/非鎖定。

某個線程要更改共享資料時,先将其鎖定,此時資源的狀态為“鎖定”,其他線程不能更改;直到該線程釋放資源,将資源的狀态變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次隻有一個線程進行寫入操作,進而保證了多線程情況下資料的正确性。

threading子產品中定義了Lock類,可以友善的處理鎖定:

#建立鎖
mutex = threading.Lock()
#鎖定
mutex.acquire([blocking])
#釋放
mutex.release()
           

其中,鎖定方法acquire可以有一個blocking參數。

• 如果設定blocking為True,則目前線程會堵塞,直到擷取到這個鎖為止(如果沒有指定,那麼預設為True)

• 如果設定blocking為False,則目前線程不會堵塞

使用互斥鎖實作上面的例子的代碼如下:

from threading import Thread, Lock
import time

g_num = 

def test1():
    global g_num
    for i in range():
        #True表示堵塞 即如果這個鎖在上鎖之前已經被上鎖了,那麼這個線程會在這裡一直等待到解鎖為止
        #False表示非堵塞,即不管本次調用能夠成功上鎖,都不會卡在這,而是繼續執行下面的代碼
        mutexFlag = mutex.acquire(True)
        if mutexFlag:
            g_num += 
            mutex.release()

    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range():
        mutexFlag = mutex.acquire(True) #True表示堵塞
        if mutexFlag:
            g_num += 
            mutex.release()

    print("---test2---g_num=%d"%g_num)

#建立一個互斥鎖
#這個所預設是未上鎖的狀态
mutex = Lock()

p1 = Thread(target=test1)
p1.start()


p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)
           

運作結果:

---g_num=13557---
---test2---g_num=1738288
---test1---g_num=2000000
           

示例:模拟售票系統

import threading  
import time
import os


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


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


#全局變量
i =                       # 初始化票數
lock = threading.Lock()     # 建立鎖


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

if __name__ == '__main__':
    main()
           

運作結果:

:now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
 :now left: 
Thread_id   No more tickets
           

上鎖解鎖過程

當一個線程調用鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀态。

每次隻有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變為“blocked”狀态,稱為“阻塞”,直到擁有鎖的線程調用鎖的release()方法釋放鎖之後,鎖進入“unlocked”狀态。

線程排程程式從處于同步阻塞狀态的線程中選擇一個來獲得鎖,并使得該線程進入運作(running)狀态。

總結

鎖的好處:

• 確定了某段關鍵代碼隻能由一個線程從頭到尾完整地執行

鎖的壞處:

• 阻止了多線程并發執行,包含鎖的某段代碼實際上隻能以單線程模式執行,效率就大大地下降了

• 由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖擷取對方持有的鎖時,可能會造成死鎖

7. 死鎖

定義

線上程間共享多個資源的時候,如果兩個線程分别占有一部分資源并且同時等待對方的資源,就會造成死鎖。

盡管死鎖很少發生,但一旦發生就會造成應用的停止響應。

示例:

import threading
import time

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

            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()
            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()
           

此時已經進入到了死鎖狀态,可以使用ctrl-z退出

說明:

Python系統程式設計--線程1.多線程-threading2. threading注意點3. 多線程-共享全局變量4. 程式VS線程5. 同步的概念6. 互斥鎖7. 死鎖8. 多線程-非共享資料9. 同步應用10. 生産者與消費者模式11.ThreadLocal12. 異步

避免死鎖:

• 程式設計時要盡量避免(銀行家算法)

• 添加逾時時間等

8. 多線程-非共享資料

**

對于全局變量,在多線程中要格外小心,否則容易造成資料錯亂的情況發生**

8.1 非全局變量是否要加鎖呢?

示例1:

import threading
import time


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

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


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

運作結果:

線程(Thread-),num=
線程(Thread-),num=
           

示例2:

import threading
from time import sleep

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


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

t1.start()
t2.start()
           

運作結果:

---(<Thread(Thread-2, started 5276)>)--num=2
---(<Thread(Thread-1, started 4828)>)--num=2
           

總結:

• 在多線程開發中,全局變量是多個線程都共享的資料,而局部變量等是各自線程的,是非共享的

9. 同步應用

示例:多個線程有序執行

from threading import Thread,Lock
from time import sleep

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

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

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

#使用Lock建立出的鎖預設沒有“鎖上”
lock1 = Lock()
#建立另外一把鎖,并且“鎖上”
lock2 = Lock()
lock2.acquire()
#建立另外一把鎖,并且“鎖上”
lock3 = Lock()
lock3.acquire()

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

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

運作結果:

------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
...省略...
           

總結:

• 可以使用互斥鎖完成多個任務,有序的程序工作,這就是線程的同步

10. 生産者與消費者模式

10.1

隊列:先進先出

棧:先進後出

Python的Queue子產品中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(後入先出)隊列LifoQueue,和優先級隊列PriorityQueue。這些隊列都實作了鎖原語(可以了解為原子操作,即要麼不做,要麼就做完),能夠在多線程中直接使用。可以使用隊列來實作線程間的同步。

用FIFO隊列實作上述生産者與消費者問題的代碼如下:

import threading
import time

#python2中
#from queue import Queue

#python3中
from queue import Queue

class Producer(threading.Thread):
    def run(self):
        global queue
        count = 
        while True:
            if queue.qsize() < :
                for i in range():
                    count = count +
                    msg = '生成産品'+str(count)
                    queue.put(msg)
                    print(msg)
            time.sleep()

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


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

    for i in range():
        queue.put('初始産品'+str(i))
    for i in range():
        p = Producer()
        p.start()
    for i in range():
        c = Consumer()
        c.start()
           

10.2 Queue的說明

  1. 對于Queue,在多線程通信之間扮演重要的角色
  2. 添加資料到隊列中,使用put()方法
  3. 從隊列中取資料,使用get()方法
  4. 判斷隊列中是否還有資料,使用qsize()方法

10.3 生産者消費者模式

• 什麼是生産者消費者模式

生産者消費者模式是通過一個容器來解決生産者和消費者的強耦合問題。生産者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,是以生産者生産完資料之後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生産者要資料,而是直接從阻塞隊列裡取,阻塞隊列就相當于一個緩沖區,平衡了生産者和消費者的處理能力。

這個阻塞隊列就是用來給生産者和消費者解耦的。縱觀大多數設計模式,都會找一個第三者出來進行解耦。

• 為什麼要使用生産者和消費者模式

線上程世界裡,生産者就是生産資料的線程,消費者就是消費資料的線程。在多線程開發當中,如果生産者處理速度很快,而消費者處理速度很慢,那麼生産者就必須等待消費者處理完,才能繼續生産資料。同樣的道理,如果消費者的處理能力大于生産者,那麼消費者就必須等待生産者。為了解決這個問題于是引入了生産者和消費者模式。

11.ThreadLocal

在多線程環境下,每個線程都有自己的資料。一個線程使用自己的局部變量比使用全局變量好,因為局部變量隻有線程自己能看見,不會影響其他線程,而全局變量的修改必須加鎖。

11.1 使用函數傳參的方法

但是局部變量也有問題,就是在函數調用的時候,傳遞起來很麻煩:

def process_student(name):
    std = Student(name)
    # std是局部變量,但是每個函數都要用它,是以必須傳進去:
    do_task_1(std)
    do_task_2(std)

def do_task_1(std):
    do_subtask_1(std)
    do_subtask_2(std)

def do_task_2(std):
    do_subtask_2(std)
    do_subtask_2(std)
           

每個函數一層一層調用都這麼傳參數那還得了?用全局變量?也不行,因為每個線程處理不同的Student對象,不能共享。

11.2 使用全局字典

如果用一個全局dict存放所有的Student對象,然後以thread自身作為key獲得線程對應的Student對象如何?

global_dict = {}

def std_thread(name):
    std = Student(name)
    # 把std放到全局變量global_dict中:
    global_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():
    # 不傳入std,而是根據目前線程查找:
    std = global_dict[threading.current_thread()]
    ...

def do_task_2():
    # 任何函數都可以查找出目前線程的std變量:
    std = global_dict[threading.current_thread()]
    ...
           

這種方式理論上是可行的,它最大的優點是消除了std對象在每層函數中的傳遞問題,但是,每個函數擷取std的代碼有點low。

有沒有更簡單的方式?

11.3 使用ThreadLocal

ThreadLocal應運而生,不用查找dict,ThreadLocal幫你自動做這件事:

import threading

# 建立全局ThreadLocal對象:
local_school = threading.local()

def process_student():
    # 擷取目前線程關聯的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()
t1.join()
t2.join()
           

運作結果:

Hello, yongGe (in Thread-A)
Hello, 老王 (in Thread-B)
           

說明

全局變量local_school就是一個ThreadLocal對象,每個Thread對它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不幹擾,也不用管理鎖的問題,ThreadLocal内部會處理。

可以了解為全局變量local_school是一個dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。

ThreadLocal最常用的地方就是為每個線程綁定一個資料庫連接配接,HTTP請求,使用者身份資訊等,這樣一個線程的所有調用到的處理函數都可以非常友善地通路這些資源。

11.4 總結

一個ThreadLocal變量雖然是全局變量,但每個線程都隻能讀寫自己線程的獨立副本,互不幹擾。ThreadLocal解決了參數在一個線程中各個函數之間互相傳遞的問題

12. 異步

• 同步調用就是你 喊 你朋友吃飯 ,你朋友在忙 ,你就一直在那等,等你朋友忙完了 ,你們一起去

• 異步調用就是你 喊 你朋友吃飯 ,你朋友說知道了 ,待會忙完去找你 ,你就去做别的了。

示例:

from multiprocessing import Pool
import time
import os

def test():
    print("---程序池中的程序---pid=%d,ppid=%d--"%(os.getpid(),os.getppid()))
    for i in range():
        print("----%d---"%i)
        time.sleep()
    return "hahah"

def test2(args):
    print("---callback func--pid=%d"%os.getpid())
    print("---callback func--args=%s"%args)
if __name__ == '__main__':

    pool = Pool()
    pool.apply_async(func=test,callback=test2)

    time.sleep()

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

運作結果:

---程序池中的程序---pid=4544,ppid=1868--
----0---
----1---
----2---
---callback func--pid=1868
---callback func--args=hahah
----主程序-pid=1868----