Hike News
Hike News

Python-多任務執行-多線程(threading)

Preface:

  • 要同時間執行兩個以上的函數,需用到多任務執行的模塊,例如threading模塊
  • 線程(Thread)是實現多任務的一種手段

Introduction:

  • python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝,可以更加方便的被使用

Notice:

Usage:

  • threading模塊中有一個Thread

    Example I:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import threading
    import time

    def sayHello():
    print("Hello")
    time.sleep(1)

    if __name__ == '__main__':
    for i in range(5):
    #創建一個線程的物件
    t = threading.Thread(target=sayHello)
    #啟動線程
    t.start()
  • 主線程會等待子線程執行結束之後才會結束程序

查看線程信息(enumerate)

  • 使用threading模塊下的enumerate可獲取當前程序所有線程信息(包括主線程),返回一個列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import threading
    from time import sleep

    def sing():
    print("I'm singing")
    sleep(1)

    def dance():
    print("I'm dancing")
    sleep(1)

    if __name__ == '__main__':
    print("In the Main")
    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 <= 1:
    break
    sleep(0.5)
  • 主線程會等到所有子線程都結束後才結束主線程

  • 當調用Thread的時候,不會創建線程
    • 使用Thread創建出來的線程物件(線程實例化對象)調用start方法的時候:
      1. 才會創建線程
      2. 並讓線程開始運行

result

1
2
3
4
5
6
In the Main
I'm singing
I'm dancing
當前線程數為:3
當前線程數為:3
當前線程數為:1

通過繼承Thread類創建線程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading
import time

class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)

#Thread類中的name屬性保存了當前線程的名字
msg = "I'm " + self.name + "@" +str(i)

print(msg)

if __name__ == '__main__':
#創建了一個線程物件(線程實例化對象)
t = MyThread()
t.start()
  • 上面使用t = MyThread創建一個線程物件,其與使用threading.Thread()創建線程物件沒有差別
  • 使用繼承的方法創建Thread,通常用於執行複雜操作時,也就是無法完全用threading.Thread(target=函數名)完整執行所有動作時使用
  • 要執行的動作需寫在類的run方法中(必須定義)
    • 線程物件執行start方法時,會調用run方法
    • run方法中內容是甚麼,線程就執行什麼
    • run方法執行完後,線程即結束

共享全局變量(重要)

  • 多線程之間是共享全局變量的

共享全局變量遇到的問題

  • 共享全局變量會遇到資源競爭的問題
    • 兩個以上的線程對同一變量進行操作

線程同步(協同步調)

  • 線程同步是解決資源競爭問題的方法
  • 按預定的先後次序運行
    • 可理解為線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,於是停下來,示意B運行,B執行
  • 必須遵守原子性操作
    • 牽涉到多線程的操作時,必須將以下三步全部執行完成,否則就不執行
      1. 獲取值
      2. 修改值
      3. 儲存值

互斥鎖

  • 當多個線程幾乎同時修改某一個共享數據時,需要進行同步控制
  • 最簡單的同步機制:引入互斥鎖
  • 互斥鎖為資源引入一個狀態:鎖定/非鎖定
    • 在執行時只允許一個線程進入獲取資源,別的線程無法進入
  • threading模塊中定義了Lock
  • 上鎖最好上越少代碼越好
  • 需注意死鎖狀況發生
    • 避免死鎖:
      • 程序盡量避免使用計時的方式(銀行家演算法)
      • 添加超時(timeout)時間等
1
2
3
4
5
6
7
8
#創建鎖物件
mutex = threading.Lock()

#鎖定
mutex.acquire()

#釋放
mutex.release()
  • 如果鎖物件之前是未上鎖的,那麼調用acquire方法便不會堵塞
  • 如果在調用acquire對鎖物件上鎖之前,已經被其他線程先行上鎖,會堵塞到鎖物件被release為止

未上鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import threading
number = 0

def func1(n):
for i in range(n):
add_one()

def func2(n):
for i in range(n):
add_one()

def add_one():
global number
number +=1

if __name__ == '__main__':
t1 = threading.Thread(target=func1,args=(1000000,))
t2 = threading.Thread(target = func2,args=(1000000,))

t1.start()
t2.start()
t1.join() #主線程等待線程完成再繼續往下執行
t2.join()
print("in the main Thread, number=",number)

result

  1. 1
    in the main Thread, number= 1660809
  2. 1
    in the main Thread, number= 1594271

3.

1
in the main Thread, number= 1704507

有上鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
number = 0

def func1(n):
for i in range(n):
add_one()

def func2(n):
for i in range(n):
add_one()

def add_one():
global number
Lock.acquire()
number +=1
Lock.release()

if __name__ == '__main__':
Lock = threading.Lock()
t1 = threading.Thread(target=func1,args=(1000000,))
t2 = threading.Thread(target = func2,args=(1000000,))

t1.start()
t2.start()
t1.join()
t2.join()
print("in the main Thread, number=",number)

result

1
in the main Thread, number= 2000000

傳遞參數

  • 使用線程執行函數時傳遞參數(args)
    • 指定將來創建線程物件時,傳遞什麼參數過去
  • threading.Thread(taget=函數名,args=(參數))
    • 請注意args參數接受必須為一元組(重要)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import threading
import time

def func1(temp):
temp.append(33)
print("-----in test1 %s"%temp)

def func2():
print("----- in test2 Thread")

g_nums = [11,22]

if __name__ == '__main__':
t1 = threading.Thread(target=func1,args=(g_nums,))
t2 = threading.Thread(target = func2)

t1.start()
time.sleep(1)

t2.start()
time.sleep(1)
print("int the main Thread")

result

1
2
3
-----in test1 [11, 22, 33]
----- in test2 Thread
int the main Thread