1 單進(jìn)程單線程:一個(gè)人在一個(gè)桌子上吃菜。 2 單進(jìn)程多線程:多個(gè)人在同一個(gè)桌子上一起吃菜。 3 多進(jìn)程單線程:多個(gè)人每個(gè)人在自己的桌子上吃菜。 多線程的問題是多個(gè)人同時(shí)吃一道菜的時(shí)候容易發(fā)生爭搶,例如兩個(gè)人同時(shí)夾一個(gè)菜,一個(gè)人剛伸出筷子,結(jié)果伸到的時(shí)候已經(jīng)被夾走菜了。。。此時(shí)就必須等一個(gè)人夾一口之后,在還給另外一個(gè)人夾菜,也就是說資源共享就會(huì)發(fā)生沖突爭搶。 二、線程threading是用來提供在一個(gè)進(jìn)程內(nèi)實(shí)現(xiàn)多線程的編程模塊. 前面我們學(xué)習(xí)了多進(jìn)程編程. 完成多任務(wù), 也可以在一個(gè)進(jìn)程內(nèi)使用多個(gè)線程. 一個(gè)進(jìn)程至少包括一個(gè)線程, 這個(gè)線程我們稱之為主線程. 在主線程中開啟的其他線程我們稱之為子線程. 一個(gè)進(jìn)程內(nèi)的所有線程之間可以直接共享資源, 所以線程間的通信要比進(jìn)程間通信方便了很多. python的thread模塊是比較底層的模塊,python的threading模塊是對(duì)thread做了一些包裝的,可以更加方便的被使用
單線程示例: import timedef say_sorry():
print("親愛的,我錯(cuò)了,我能吃飯了嗎?")
time.sleep(1)if __name__ == "__main__": for i in range(5):
say_sorry()
多線程示例: import threadingimport timedef say_sorry():
print("親愛的,我錯(cuò)了,我能吃飯了嗎?")
time.sleep(1)if __name__ == "__main__": for i in range(5):
t = threading.Thread(target=say_sorry)
t.start() #啟動(dòng)線程,即讓線程開始執(zhí)行
說明: 可以明顯看出使用了多線程并發(fā)的操作,花費(fèi)時(shí)間要短很多 創(chuàng)建好的線程,需要調(diào)用start() 方法來啟動(dòng)
2.1 threadingPython3 線程中常用的兩個(gè)模塊為: 2.2 Thread類Thread類表示在單獨(dú)的控制線程中運(yùn)行的活動(dòng)。有兩種方法可以指定這種活動(dòng): 2.2.1、給構(gòu)造函數(shù)傳遞回調(diào)對(duì)象mthread=threading.Thread(target=xxxx,args=(xxxx))
mthread.start()
2.2.2、在子類中重寫run() 方法 這里舉個(gè)小例子:import threadingimport timeclass MyThread(threading.Thread): def __init__(self,arg):
super(MyThread, self).__init__()#注意:一定要顯式的調(diào)用父類的初始化函數(shù)。
self.arg=arg def run(self):#定義每個(gè)線程要運(yùn)行的函數(shù)
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、多線程之間共享全局變量多線程和多進(jìn)程最大的不同在于,多進(jìn)程中,同一個(gè)變量,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響, 而多線程中,所有變量都由所有線程共享,下面觀察一下兩條線程中共享同一份數(shù)據(jù)。 from threading import Threadimport time
g_num = 100 # 定義全局變量g_numdef work1():
num = 1 # 定義局部變量num global g_num # 關(guān)鍵字global標(biāo)記全局變量g_num for i in range(3):
g_num += 1 # 更改全局變量的值
print("---子線程1---work1函數(shù)---g_num:%d" % g_num)def work2():
num = 2 global g_num # 關(guān)鍵字global標(biāo)記全局變量g_num
print("\t---子線程2---work2函數(shù)---個(gè)g_num:%d" % g_num)if __name__ == "__main__":
print("啟動(dòng)線程之前:g_num:%d" % g_num)
t1 = Thread(target=work1) # 創(chuàng)建t1子線程,分配任務(wù)work1
t2 = Thread(target=work2) # 創(chuàng)建t2子線程,分配任務(wù)work2
t1.start() # 啟動(dòng)t1線程
time.sleep(1) # 等待t1線程執(zhí)行完畢,觀察t2線程中打印的全局變量是否發(fā)生改變
t2.start() # 啟動(dòng)t2線程
2.2.4、全局變量作為參數(shù)傳遞將列表作為參數(shù)傳遞進(jìn)來,在參數(shù)末尾追加元素 from threading import Threadimport time
g_list = [10, 20, 30]def work1(list): for i in range(3):
list.append(i) # 更改參數(shù)的值,在列表末尾追加元素
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,)) # 創(chuàng)建線程t1 將g_list作為參數(shù)傳遞進(jìn)去 執(zhí)行函數(shù)work1
t2 = Thread(target=work2, args=(g_list,)) # 創(chuàng)建線程t2 將g_list作為參數(shù)傳遞進(jìn)去 執(zhí)行函數(shù)work2
t1.start()
time.sleep(1)
t2.start()
將列表作為參數(shù)傳遞進(jìn)來,將參數(shù)重置 from threading import Threadimport time
g_list = [10, 20 ,30]def work1(list): for i in range(3): # list.append(i)
list = [1, 2, 3] # 重置參數(shù)
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,)) # 創(chuàng)建線程t1 將g_list作為參數(shù)傳遞進(jìn)去 執(zhí)行函數(shù)work1
t2 = Thread(target=work2, args=(g_list,)) # 創(chuàng)建線程t2 將g_list作為參數(shù)傳遞進(jìn)去 執(zhí)行函數(shù)work2
t1.start()
time.sleep(1)
t2.start()
2.2.5、線程的鎖多線程和多進(jìn)程最大的不同在于,多進(jìn)程中,同一個(gè)變量,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個(gè)變量都可以被任何一個(gè)線程修改,因此,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容給改亂了。 來看看多個(gè)線程同時(shí)操作一個(gè)變量怎么把內(nèi)容給改亂了: import time, threading# 假定這是你的銀行存款:
balance = 0def change_it(n): # 先存后取,結(jié)果應(yīng)該為0: global balance
balance = balance + n
balance = balance - ndef 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)
每次執(zhí)行的結(jié)果不一定是個(gè)啥,究其原因,是因?yàn)樾薷?code>balance需要多條語句,而執(zhí)行這幾條語句時(shí),線程可能中斷,從而導(dǎo)致多個(gè)線程把同一個(gè)對(duì)象的內(nèi)容改亂了。 兩個(gè)線程同時(shí)一存一取,就可能導(dǎo)致余額不對(duì),你肯定不希望你的銀行存款莫名其妙地變成了負(fù)數(shù),所以,我們必須確保一個(gè)線程在修改balance 的時(shí)候,別的線程一定不能改。 如果我們要確保balance 計(jì)算正確,就要給change_it() 上一把鎖,當(dāng)某個(gè)線程開始執(zhí)行change_it() 時(shí),我們說,該線程因?yàn)楂@得了鎖,因此其他線程不能同時(shí)執(zhí)行change_it() ,只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由于鎖只有一個(gè),無論多少線程,同一時(shí)刻最多只有一個(gè)線程持有該鎖,所以,不會(huì)造成修改的沖突。創(chuàng)建一個(gè)鎖就是通過threading.Lock() 來實(shí)現(xiàn): #鎖的使用
mutex = threading.Lock() #創(chuàng)建鎖
mutex.acquire([timeout]) #鎖定
mutex.release() #釋放
import time, threading
balance = 0
lock = threading.Lock()def change_it(n): # 先存后取,結(jié)果應(yīng)該為0: global balance
balance = balance + n
balance = balance - ndef 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)
|