接上一章,進程和線程之間可以存在哪些形式呢?
1 單進程單線程:一個人在一個桌子上吃菜。
2 單進程多線程:多個人在同一個桌子上一起吃菜。
3 多進程單線程:多個人每個人在自己的桌子上吃菜。
多線程的問題是多個人同時吃一道菜的時候容易發生爭搶,例如兩個人同時夾一個菜,一個人剛伸出筷子,結果伸到的時候已經被夾走菜了。。。此時就必須等一個人夾一口之后,在還給另外一個人夾菜,也就是說資源共享就會發生沖突爭搶。
二、線程
threading是用來提供在一個進程內實現多線程的編程模塊.
前面我們學習了多進程編程.
完成多任務, 也可以在一個進程內使用多個線程. 一個進程至少包括一個線程, 這個線程我們稱之為主線程. 在主線程中開啟的其他線程我們稱之為子線程.
一個進程內的所有線程之間可以直接共享資源, 所以線程間的通信要比進程間通信方便了很多.
python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用
單線程示例:
import time
def say_sorry():
print("親愛的,我錯了,我能吃飯了嗎?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
say_sorry()
多線程示例:
import threading
import time
def say_sorry():
print("親愛的,我錯了,我能吃飯了嗎?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=say_sorry)
t.start() #啟動線程,即讓線程開始執行
說明:
- 可以明顯看出使用了多線程並發的操作,花費時間要短很多
- 創建好的線程,需要調用
start()
方法來啟動
2.1 threading
Python3 線程中常用的兩個模塊為:
-
_thread
thread 模塊已被廢棄。用戶可以使用 threading 模塊代替。所以,在 Python3 中不能再使用"thread" 模塊。為了兼容性,Python3 將 thread 重命名為 "_thread"
-
threading(推薦使用)
threading用於提供線程相關的操作,線程是應用程序中工作的最小單元。
2.2 Thread類
Thread類表示在單獨的控制線程中運行的活動。有兩種方法可以指定這種活動:
2.2.1、給構造函數傳遞回調對象
mthread=threading.Thread(target=xxxx,args=(xxxx))
mthread.start()
2.2.2、在子類中重寫run() 方法 這里舉個小例子:
import threading
import time
class MyThread(threading.Thread):
def __init__(self,arg):
super(MyThread, self).__init__()#注意:一定要顯式的調用父類的初始化函數。
self.arg=arg
def run(self):#定義每個線程要運行的函數
time.sleep(1)
print('the arg is:%s\r' % self.arg)
for i in range(4):
t =MyThread(i)
t.start()
print('main thread end!')
2.2.3、多線程之間共享全局變量
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響, 而多線程中,所有變量都由所有線程共享,下面觀察一下兩條線程中共享同一份數據。
from threading import Thread
import time
g_num = 100 # 定義全局變量g_num
def work1():
num = 1 # 定義局部變量num
global g_num # 關鍵字global標記全局變量g_num
for i in range(3):
g_num += 1 # 更改全局變量的值
print("---子線程1---work1函數---g_num:%d" % g_num)
def work2():
num = 2
global g_num # 關鍵字global標記全局變量g_num
print("\t---子線程2---work2函數---個g_num:%d" % g_num)
if __name__ == "__main__":
print("啟動線程之前:g_num:%d" % g_num)
t1 = Thread(target=work1) # 創建t1子線程,分配任務work1
t2 = Thread(target=work2) # 創建t2子線程,分配任務work2
t1.start() # 啟動t1線程
time.sleep(1) # 等待t1線程執行完畢,觀察t2線程中打印的全局變量是否發生改變
t2.start() # 啟動t2線程
2.2.4、全局變量作為參數傳遞
將列表作為參數傳遞進來,在參數末尾追加元素
from threading import Thread
import time
g_list = [10, 20, 30]
def work1(list):
for i in range(3):
list.append(i) # 更改參數的值,在列表末尾追加元素
print("--子線程1--work1----num:", list)
def work2(list):
print("\t--子線程2---work2---num:", list)
if __name__ == "__main__":
print("主線程訪問g_list:" , g_list)
t1 = Thread(target=work1, args=(g_list,)) # 創建線程t1 將g_list作為參數傳遞進去 執行函數work1
t2 = Thread(target=work2, args=(g_list,)) # 創建線程t2 將g_list作為參數傳遞進去 執行函數work2
t1.start()
time.sleep(1)
t2.start()
將列表作為參數傳遞進來,將參數重置
from threading import Thread
import time
g_list = [10, 20 ,30]
def work1(list):
for i in range(3):
# list.append(i)
list = [1, 2, 3] # 重置參數
print("--子線程1--work1----num:", list)
def work2(list):
print("\t--子線程2---work2---num:", list)
if __name__ == "__main__":
print("主線程訪問g_list:" , g_list)
t1 = Thread(target=work1, args=(g_list,)) # 創建線程t1 將g_list作為參數傳遞進去 執行函數work1
t2 = Thread(target=work2, args=(g_list,)) # 創建線程t2 將g_list作為參數傳遞進去 執行函數work2
t1.start()
time.sleep(1)
t2.start()
2.2.5、線程的鎖
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
來看看多個線程同時操作一個變量怎么把內容給改亂了:
import time, threading
# 假定這是你的銀行存款:
balance = 0
def change_it(n):
# 先存后取,結果應該為0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
if __name__ == "__main__":
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
每次執行的結果不一定是個啥,究其原因,是因為修改balance
需要多條語句,而執行這幾條語句時,線程可能中斷,從而導致多個線程把同一個對象的內容改亂了。
兩個線程同時一存一取,就可能導致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負數,所以,我們必須確保一個線程在修改balance
的時候,別的線程一定不能改。
如果我們要確保balance
計算正確,就要給change_it()
上一把鎖,當某個線程開始執行change_it()
時,我們說,該線程因為獲得了鎖,因此其他線程不能同時執行change_it()
,只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由於鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,所以,不會造成修改的沖突。創建一個鎖就是通過threading.Lock()
來實現:
#鎖的使用
mutex = threading.Lock() #創建鎖
mutex.acquire([timeout]) #鎖定
mutex.release() #釋放
import time, threading
balance = 0
lock = threading.Lock()
def change_it(n):
# 先存后取,結果應該為0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
if __name__ == "__main__":
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)