Python學習筆記 - 多執行緒

MADAO是不會開花的發表於2019-01-11
  • 區分程式和執行緒

    執行緒和程式是作業系統相關的概念,比如在電腦上可以一邊聽歌,一邊瀏覽網頁,當你開啟一個音樂播放器的時候就會啟動一個程式,開啟瀏覽器的時候也會啟動一個程式。

    Python學習筆記 - 多執行緒

    在音樂播放器中我們在聽歌的同時,發表一些評論或者搜尋其他歌曲。在程式裡同時執行的這些任務,叫做執行緒。一個程式裡是可以包含多個執行緒的。

  • Python多執行緒程式設計

    Python中多執行緒程式設計需要藉助的模組是:threading

    • 單執行緒和多執行緒對比:

      • 單執行緒:

        import time
        
        
        # 單執行緒
        def sayHello(index):
            print('hello!', index)
            time.sleep(1)
            print('執行完成')
        
        
        for i in range(5):
            sayHello(i)
        複製程式碼

        結果:

        Python學習筆記 - 多執行緒
        例子中看單執行緒程式執行呢就是一個接著一個執行

      • 多執行緒:

        import time
        import threading
        
        
        def sayHello(index):
            print('hello!', index)
            time.sleep(1)
            print('執行完成')
        
        
        for i in range(5):
            t1 = threading.Thread(target=sayHello, args=(i,))
            t1.start()
        複製程式碼

        結果:

        Python學習筆記 - 多執行緒

        多執行緒則像是超市的收銀臺一樣,2號收銀臺不用等1號收銀臺的顧客全部交完錢了再開始工作,可以一起工作。

  • threading.Thread

    threading.Thread函式,建立一個執行緒。

    使用help函式可以看到它接受的引數有這些:

    Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
    
    |*group* should be None; reserved for future extension when a ThreadGroup
    |class is implemented.
    |
    |*target* is the callable object to be invoked by the run()
    |method. Defaults to None, meaning nothing is called.
    |
    |*name* is the thread name. By default, a unique name is constructed of
    |the form "Thread-N" where N is a small decimal number.
    |
    |*args* is the argument tuple for the target invocation. Defaults to ().
    |
    |*kwargs* is a dictionary of keyword arguments for the target
    |invocation. Defaults to {}.
    複製程式碼

    target就是執行緒啟動後執行的函式

    args和kwargs都是這個函式的引數,args是元組型別,kwargs是關鍵字型別引數。

    name是給這個執行緒的名字,如果不傳就是Thread-1, Thread-2....

    它還有一些方法:

    • run(): 執行執行緒中的方法。
    • start(): 啟動執行緒。
    • join(): 等待執行緒結束。
    • isAlive(): 返回執行緒是否活動的。
    • getName(): 返回執行緒名。
    • setName(): 設定執行緒名。
  • threading.currentThread

    threading.currentThread可以獲得當前的執行緒資訊,例子:

    import time
    from threading import Thread, currentThread
    
    def sayHello(index):
        print(currentThread().getName(), index)
        time.sleep(1)
        print('執行完成')
    
    
    for i in range(5):
        t1 = Thread(target=sayHello, kwargs={'index': 1})
        t1.start()
    複製程式碼

    結果:

    Python學習筆記 - 多執行緒

結合Thread類的join方法,可以看下主執行緒和子執行緒之間的順序:

from threading import Thread, currentThread
import time


def do_somethong():
    print(currentThread().getName(), '開始')
    time.sleep(1)
    print(currentThread().getName(), '結束')


t1 = Thread(target=do_somethong)
t1.start()
print(currentThread().getName(), '結束')

複製程式碼

結果:

Python學習筆記 - 多執行緒

MainThread就是主執行緒,可以看到是主執行緒先結束Thread-1執行緒再結束的,如果想要主執行緒最後結束,那麼就可以使用join方法了。

from threading import Thread, currentThread
import time


def do_somethong():
    print(currentThread().getName(), '開始')
    time.sleep(1)
    print(currentThread().getName(), '結束')


t1 = Thread(target=do_somethong)
t1.start()
t1.join()  # 新增
print(currentThread().getName(), '結束')

複製程式碼

Python學習筆記 - 多執行緒

  • 多個執行緒之間的變數

from threading import Thread
a = 0


def change_num(num):
    global a
    a += 1
    a -= 1


def run_thread(num):
    for i in range(100000):
        change_num(num)


t1 = Thread(target=run_thread, args=(1,))
t2 = Thread(target=run_thread, args=(2,))

t1.start()
t2.start()
t1.join()
t2.join()

print(a)
複製程式碼

多次執行上面程式碼的結果:

Python學習筆記 - 多執行緒

a的值應該永遠是0的,但是有時候會變,多執行緒中的變數是共享的,如果同時去改一個變數,那麼有可能會將該變數改亂。原因

如果想要多個執行緒修改同一變數,那麼就需要一個鎖來確保該變數修改後的正確性。 鎖就是當一個執行緒在修改的時候,其他執行緒等待。這個執行緒修改完了,下個執行緒繼續修改。

實現這個鎖,要用到threading.lock()方法:

from threading import Thread, Lock
a = 0
thread_lock = Lock()  # 建立一個鎖


def change_num(num):
    global a
    a += 1
    a -= 1


def run_thread(num):
    for i in range(100000):
        try:
            thread_lock.acquire()  # 鎖住
            change_num(num)
        finally:
            thread_lock.release() # 解鎖


t1 = Thread(target=run_thread, args=(1,))
t2 = Thread(target=run_thread, args=(2,))

t1.start()
t2.start()
t1.join()
t2.join()

print(a)
複製程式碼
  • threading.local

執行緒之間如果使用區域性變數,那麼就可以用threading.local這個方法。例子:

from threading import Thread, currentThread, local


data = local()


def do_somethong(name):
    data.name = name
    print(currentThread().getName(), data.name)


t1 = Thread(target=do_somethong, args=('jack',))
t2 = Thread(target=do_somethong, args=('alex',))
t1.start()
t2.start()
t1.join()
t2.join()

複製程式碼

結果:

Python學習筆記 - 多執行緒

用threading.local的好處是,當執行緒中函式比較複雜的時候,也就是說不僅僅只是執行一個函式,而是多個函式,比如:

from threading import Thread
import time


def sayHelle(name):
    print('hello!%s' % name)

def sayBye(name):
    print('goodbye!%s' % name)
    
def run(name):
    sayHelle(name)
    time.sleep(1)
    sayBye(name)
    
t1 = Thread(target=run, args=('jack',))
t1 = Thread(target=run, args=('jack',))

t1.start()
t2.start()
t1.join()
t2.join()
複製程式碼

這個name引數就得一個一個的傳遞下去,這樣很不方便,threading.lock()方法就像是全域性的一個字典,每一個執行緒從裡面獲取name的時候都是各自執行緒自己存進去的name,相互不會干擾。

相關文章