摘要:某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。線程的實體包括程序數(shù)據(jù)和。包括以下信息線程狀態(tài)。當(dāng)線程不運行時,被保存的現(xiàn)場資源。用戶級線程執(zhí)行系統(tǒng)調(diào)用指令時將導(dǎo)致其所屬進(jìn)程被中斷,而內(nèi)核支持線程執(zhí)行系統(tǒng)調(diào)用指令時,只導(dǎo)致該線程被中斷。線程能夠利用的表空
操作系統(tǒng)線程理論 線程概念的引入背景
進(jìn)程
之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念,程序并不能多帶帶運行,只有將程序裝載到內(nèi)存中,系統(tǒng)為它分配資源才能運行,而這種執(zhí)行的程序就稱之為進(jìn)程。程序和進(jìn)程的區(qū)別就在于:程序是指令的集合,它是進(jìn)程運行的靜態(tài)描述文本;進(jìn)程是程序的一次執(zhí)行活動,屬于動態(tài)概念。在多道編程中,我們允許多個程序同時加載到內(nèi)存中,在操作系統(tǒng)的調(diào)度下,可以實現(xiàn)并發(fā)地執(zhí)行。這是這樣的設(shè)計,大大提高了CPU的利用率。進(jìn)程的出現(xiàn)讓每個用戶感覺到自己獨享CPU,因此,進(jìn)程就是為了在CPU上實現(xiàn)多道編程而提出的。
有了進(jìn)程為什么要有線程
進(jìn)程有很多優(yōu)點,它提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率。很多人就不理解了,既然進(jìn)程這么優(yōu)秀,為什么還要線程呢?其實,仔細(xì)觀察就會發(fā)現(xiàn)進(jìn)程還是有很多缺陷的,主要體現(xiàn)在兩點上:
進(jìn)程只能在一個時間干一件事,如果想同時干兩件事或多件事,進(jìn)程就無能為力了。
進(jìn)程在執(zhí)行的過程中如果阻塞,例如等待輸入,整個進(jìn)程就會掛起,即使進(jìn)程中有些工作不依賴于輸入的數(shù)據(jù),也將無法執(zhí)行。
如果這兩個缺點理解比較困難的話,舉個現(xiàn)實的例子也許你就清楚了:如果把我們上課的過程看成一個進(jìn)程的話,那么我們要做的是耳朵聽老師講課,手上還要記筆記,腦子還要思考問題,這樣才能高效的完成聽課的任務(wù)。而如果只提供進(jìn)程這個機制的話,上面這三件事將不能同時執(zhí)行,同一時間只能做一件事,聽的時候就不能記筆記,也不能用腦子思考,這是其一;如果老師在黑板上寫演算過程,我們開始記筆記,而老師突然有一步推不下去了,阻塞住了,他在那邊思考著,而我們呢,也不能干其他事,即使你想趁此時思考一下剛才沒聽懂的一個問題都不行,這是其二。
現(xiàn)在你應(yīng)該明白了進(jìn)程的缺陷了,而解決的辦法很簡單,我們完全可以讓聽、寫、思三個獨立的過程,并行起來,這樣很明顯可以提高聽課的效率。而實際的操作系統(tǒng)中,也同樣引入了這種類似的機制——線程。
線程的出現(xiàn)
60年代,在OS中能擁有資源和獨立運行的基本單位是進(jìn)程,然而隨著計算機技術(shù)的發(fā)展,進(jìn)程出現(xiàn)了很多弊端,一是由于進(jìn)程是資源擁有者,創(chuàng)建、撤消與切換存在較大的時空開銷,因此需要引入輕型進(jìn)程;二是由于對稱多處理機(SMP)出現(xiàn),可以滿足多個運行單位,而多個進(jìn)程并行開銷過大。
因此在80年代,出現(xiàn)了能獨立運行的基本單位——線程(Threads)。
注意:進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位.
每一個進(jìn)程中至少有一個線程。
線程與進(jìn)程的區(qū)別可以歸納為以下4點:
1)地址空間和其它資源(如打開文件):進(jìn)程間相互獨立,同一進(jìn)程的各線程間共享。某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。
2)通信:進(jìn)程間通信IPC,線程間可以直接讀寫進(jìn)程數(shù)據(jù)段(如全局變量)來進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
3)調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
4)在多線程操作系統(tǒng)中,進(jìn)程不是一個可執(zhí)行的實體。
*通過漫畫了解線程進(jìn)城
1)輕型實體
線程中的實體基本上不擁有系統(tǒng)資源,只是有一點必不可少的、能保證獨立運行的資源。
線程的實體包括程序、數(shù)據(jù)和TCB。線程是動態(tài)概念,它的動態(tài)特性由線程控制塊TCB(Thread Control Block)描述。
TCB包括以下信息:
(1)線程狀態(tài)。
(2)當(dāng)線程不運行時,被保存的現(xiàn)場資源。
(3)一組執(zhí)行堆棧。
(4)存放每個線程的局部變量主存區(qū)。
(5)訪問同一個進(jìn)程中的主存和其它資源。
用于指示被執(zhí)行指令序列的程序計數(shù)器、保留局部變量、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
復(fù)制代碼
2)獨立調(diào)度和分派的基本單位。
在多線程OS中,線程是能獨立運行的基本單位,因而也是獨立調(diào)度和分派的基本單位。由于線程很“輕”,故線程的切換非常迅速且開銷?。ㄔ谕贿M(jìn)程中的)。
3)共享進(jìn)程資源。
線程在同一進(jìn)程中的各個線程,都可以共享該進(jìn)程所擁有的資源,這首先表現(xiàn)在:所有線程都具有相同的進(jìn)程id,這意味著,線程可以訪問該進(jìn)程的每一個內(nèi)存資源;此外,還可以訪問進(jìn)程所擁有的已打開文件、定時器、信號量機構(gòu)等。由于同一個進(jìn)程內(nèi)的線程共享內(nèi)存和文件,所以線程之間互相通信不必調(diào)用內(nèi)核。
4)可并發(fā)執(zhí)行。
在一個進(jìn)程中的多個線程之間,可以并發(fā)執(zhí)行,甚至允許在一個進(jìn)程中所有線程都能并發(fā)執(zhí)行;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行,充分利用和發(fā)揮了處理機與外圍設(shè)備并行工作的能力。
開啟一個字處理軟件進(jìn)程,該進(jìn)程肯定需要辦不止一件事情,比如監(jiān)聽鍵盤輸入,處理文字,定時自動將文字保存到硬盤,這三個任務(wù)操作的都是同一塊數(shù)據(jù),因而不能用多進(jìn)程。只能在一個進(jìn)程里并發(fā)地開啟三個線程,如果是單線程,那就只能是,鍵盤輸入時,不能處理文字和自動保存,自動保存時又不能輸入和處理文字。
內(nèi)存中的線程多個線程共享同一個進(jìn)程的地址空間中的資源,是對一臺計算機上多個進(jìn)程的模擬,有時也稱線程為輕量級的進(jìn)程。
而對一臺計算機上多個進(jìn)程,則共享物理內(nèi)存、磁盤、打印機等其他物理資源。多線程的運行也多進(jìn)程的運行類似,是cpu在多個線程之間的快速切換。
不同的進(jìn)程之間是充滿敵意的,彼此是搶占、競爭cpu的關(guān)系,如果迅雷會和QQ搶資源。而同一個進(jìn)程是由一個程序員的程序創(chuàng)建,所以同一進(jìn)程內(nèi)的線程是合作關(guān)系,一個線程可以訪問另外一個線程的內(nèi)存地址,大家都是共享的,一個線程干死了另外一個線程的內(nèi)存,那純屬程序員腦子有問題。
類似于進(jìn)程,每個線程也有自己的堆棧,不同于進(jìn)程,線程庫無法利用時鐘中斷強制線程讓出CPU,可以調(diào)用thread_yield運行線程自動放棄cpu,讓另外一個線程運行。
線程通常是有益的,但是帶來了不小程序設(shè)計難度,線程的問題是:
1. 父進(jìn)程有多個線程,那么開啟的子線程是否需要同樣多的線程
2. 在同一個進(jìn)程中,如果一個線程關(guān)閉了文件,而另外一個線程正準(zhǔn)備往該文件內(nèi)寫內(nèi)容呢?
因此,在多線程的代碼中,需要更多的心思來設(shè)計程序的邏輯、保護(hù)程序的數(shù)據(jù)。
線程的實現(xiàn)可以分為兩類:用戶級線程(User-Level Thread)和內(nèi)核線線程(Kernel-Level Thread),后者又稱為內(nèi)核支持的線程或輕量級進(jìn)程。在多線程操作系統(tǒng)中,各個系統(tǒng)的實現(xiàn)方式并不相同,在有的系統(tǒng)中實現(xiàn)了用戶級線程,有的系統(tǒng)中實現(xiàn)了內(nèi)核級線程。
用戶級線程
內(nèi)核的切換由用戶態(tài)程序自己控制內(nèi)核切換,不需要內(nèi)核干涉,少了進(jìn)出內(nèi)核態(tài)的消耗,但不能很好的利用多核Cpu。
內(nèi)核級線程
內(nèi)核級線程:切換由內(nèi)核控制,當(dāng)線程進(jìn)行切換的時候,由用戶態(tài)轉(zhuǎn)化為內(nèi)核態(tài)。切換完畢要從內(nèi)核態(tài)返回用戶態(tài);可以很好的利用smp,即利用多核cpu。windows線程就是這樣的。
用戶級與內(nèi)核級線程的對比
1.用戶級線程和內(nèi)核級線程的區(qū)別
1 內(nèi)核支持線程是OS內(nèi)核可感知的,而用戶級線程是OS內(nèi)核不可感知的。 2 用戶級線程的創(chuàng)建、撤消和調(diào)度不需要OS內(nèi)核的支持,是在語言(如Java)這一級處理的;而內(nèi)核支持線程的創(chuàng)建、撤消和調(diào)度都需OS內(nèi)核提供支持,而且與進(jìn)程的創(chuàng)建、撤消和調(diào)度大體是相同的。 3 用戶級線程執(zhí)行系統(tǒng)調(diào)用指令時將導(dǎo)致其所屬進(jìn)程被中斷,而內(nèi)核支持線程執(zhí)行系統(tǒng)調(diào)用指令時,只導(dǎo)致該線程被中斷。 4 在只有用戶級線程的系統(tǒng)內(nèi),CPU調(diào)度還是以進(jìn)程為單位,處于運行狀態(tài)的進(jìn)程中的多個線程,由用戶程序控制線程的輪換運行;在有內(nèi)核支持線程的系統(tǒng)內(nèi),CPU調(diào)度則以線程為單位,由OS的線程調(diào)度程序負(fù)責(zé)線程的調(diào)度。 5 用戶級線程的程序?qū)嶓w是運行在用戶態(tài)下的程序,而內(nèi)核支持線程的程序?qū)嶓w則是可以運行在任何狀態(tài)下的程序。
2.內(nèi)核線程的優(yōu)缺點
優(yōu)點:當(dāng)有多個處理機時,一個進(jìn)程的多個線程可以同時執(zhí)行。 缺點:由內(nèi)核進(jìn)行調(diào)度。
3.用戶級線程的優(yōu)缺點
優(yōu)點: 線程的調(diào)度不需要內(nèi)核直接參與,控制簡單。 可以在不支持線程的操作系統(tǒng)中實現(xiàn)。 創(chuàng)建和銷毀線程、線程切換代價等線程管理的代價比內(nèi)核線程少得多。 允許每個進(jìn)程定制自己的調(diào)度算法,線程管理比較靈活。 線程能夠利用的表空間和堆??臻g比內(nèi)核級線程多。 同一進(jìn)程中只能同時有一個線程在運行,如果有一個線程使用了系統(tǒng)調(diào)用而阻塞,那么整個進(jìn)程都會被掛起。另外,頁面失效也會產(chǎn)生同樣的問題。 缺點: 資源調(diào)度按照進(jìn)程進(jìn)行,多個處理機下,同一個進(jìn)程中的線程只能在同一個處理機下分時復(fù)用
混合實現(xiàn)
用戶級與內(nèi)核級的多路復(fù)用,內(nèi)核同一調(diào)度內(nèi)核線程,每個內(nèi)核線程對應(yīng)n個用戶線程
全局解釋器鎖GIL
Python代碼的執(zhí)行由Python虛擬機(也叫解釋器主循環(huán))來控制。Python在設(shè)計之初就考慮到要在主循環(huán)中,同時只有一個線程在執(zhí)行。雖然 Python 解釋器中可以“運行”多個線程,但在任意時刻只有一個線程在解釋器中運行。
對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。
在多線程環(huán)境中,Python 虛擬機按以下方式執(zhí)行:
a、設(shè)置 GIL;
b、切換到一個線程去運行;
c、運行指定數(shù)量的字節(jié)碼指令或者線程主動讓出控制(可以調(diào)用 time.sleep(0));
d、把線程設(shè)置為睡眠狀態(tài);
e、解鎖 GIL;
d、再次重復(fù)以上所有步驟。
在調(diào)用外部代碼(如 C/C++擴展函數(shù))的時候,GIL將會被鎖定,直到這個函數(shù)結(jié)束為止(由于在這期間沒有Python的字節(jié)碼被運行,所以不會做線程切換)編寫擴展的程序員可以主動解鎖GIL。
python線程模塊的選擇
Python提供了幾個用于多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊允許程序員創(chuàng)建和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級別、功能更強的線程管理的功能。Queue模塊允許用戶創(chuàng)建一個可以用于多個線程之間共享數(shù)據(jù)的隊列數(shù)據(jù)結(jié)構(gòu)。
避免使用thread模塊,因為更高級別的threading模塊更為先進(jìn),對線程的支持更為完善,而且使用thread模塊里的屬性有可能會與threading出現(xiàn)沖突;其次低級別的thread模塊的同步原語很少(實際上只有一個),而threading模塊則有很多;再者,thread模塊中當(dāng)主線程結(jié)束時,所有的線程都會被強制結(jié)束掉,沒有警告也不會有正常的清除工作,至少threading模塊能確保重要的子線程退出后進(jìn)程才退出。
thread模塊不支持守護(hù)線程,當(dāng)主線程退出時,所有的子線程不論它們是否還在工作,都會被強行退出。而threading模塊支持守護(hù)線程,守護(hù)線程一般是一個等待客戶請求的服務(wù)器,如果沒有客戶提出請求它就在那等著,如果設(shè)定一個線程為守護(hù)線程,就表示這個線程是不重要的,在進(jìn)程退出的時候,不用等待這個線程退出。
multiprocess模塊的完全模仿了threading模塊的接口,二者在使用層面,有很大的相似性,見官網(wǎng)鏈接:
線程的創(chuàng)建Threading.Thread類
1.線程的創(chuàng)建
創(chuàng)建線程的方式1:
#!/usr/bin/env python # -*- coding:utf-8 -*- from threading import Thread import time def sayhi(name): time.sleep(2) print("%s say hello" %name) if __name__ == "__main__": t=Thread(target=sayhi,args=("egon",)) t.start() print("主線程")
創(chuàng)建線程的方式2:
from threading import Thread import time class Sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print("%s say hello" % self.name) if __name__ == "__main__": t = Sayhi("egon") t.start() print("主線程")
2.多線程與多進(jìn)程
pid的比較
from threading import Thread from multiprocessing import Process import os def work(): print("hello",os.getpid()) if __name__ == "__main__": #part1:在主進(jìn)程下開啟多個線程,每個線程都跟主進(jìn)程的pid一樣 t1=Thread(target=work) t2=Thread(target=work) t1.start() t2.start() print("主線程/主進(jìn)程pid",os.getpid()) #part2:開多個進(jìn)程,每個進(jìn)程都有不同的pid p1=Process(target=work) p2=Process(target=work) p1.start() p2.start() print("主線程/主進(jìn)程pid",os.getpid())
開啟效率的較量
import time from multiprocessing import Process from threading import Thread n = 10 def func(i): global n n -= 1 if __name__ == "__main__": start = time.time() t_lst = [] for i in range(100): t = Thread(target=func,args=(i,)) t.start() t_lst.append(t) for t in t_lst:t.join() print("線程 ",time.time() - start) start = time.time() p_lst = [] for i in range(100): p = Process(target=func,args=(i,)) p.start() p_lst.append(p) for p in p_lst: p.join() print("進(jìn)程 :",time.time() - start)
內(nèi)存數(shù)據(jù)的共享問題
from threading import Thread from multiprocessing import Process def work(): global n n=0 print("子線程/子進(jìn)程",n) if __name__ == "__main__": n=100 p=Process(target=work) p.start() p.join() print("主",n) #毫無疑問子進(jìn)程p已經(jīng)將自己的全局的n改成了0,但改的僅僅是它自己的,查看父進(jìn)程的n仍然為100 n=1 t=Thread(target=work) t.start() t.join() print("主",n) #查看結(jié)果為0,因為同一進(jìn)程內(nèi)的線程之間共享進(jìn)程內(nèi)的數(shù)據(jù)
多線程實現(xiàn)socket
server端:
from threading import Thread import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(("127.0.0.1",8080)) s.listen(5) def action(conn,addr): while True: data=conn.recv(1024) print("來自客戶端:{addr}消息為:{data}".format(addr=addr,data=data.decode("utf-8"))) conn.send(data) if __name__ == "__main__": while True: conn,addr=s.accept() p=Thread(target=action,args=(conn,addr)) p.start()
client端:
import socket ip_port = ("127.0.0.1",8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(ip_port) while True: msg=input(">>: ").strip() if not msg:continue s.send(msg.encode("utf-8")) data=s.recv(1024).decode("utf-8") print("來自服務(wù)端:{ip},消息為:{data}".format(ip=ip_port,data=data))
Thread類的其他方法
Thread實例對象的方法 # isAlive(): 返回線程是否活動的。 # getName(): 返回線程名。 # setName(): 設(shè)置線程名。 threading模塊提供的一些方法: # threading.currentThread(): 返回當(dāng)前的線程變量。 # threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啟動后、結(jié)束前,不包括啟動前和終止后的線程。 # threading.activeCount(): 返回正在運行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果。
守護(hù)線程
無論是進(jìn)程還是線程,都遵循:守護(hù)xx會等待主xx運行完畢后被銷毀。需要強調(diào)的是:運行完畢并非終止運行
1 主進(jìn)程在其代碼結(jié)束后就已經(jīng)算運行完畢了(守護(hù)進(jìn)程在此時就被回收),然后主進(jìn)程會一直等非守護(hù)的子進(jìn)程都運行完畢后回收子進(jìn)程的資源(否則會產(chǎn)生僵尸進(jìn)程),才會結(jié)束,
2 主線程在其他非守護(hù)線程運行完畢后才算運行完畢(守護(hù)線程在此時就被回收)。因為主線程的結(jié)束意味著進(jìn)程的結(jié)束,進(jìn)程整體的資源都將被回收,而進(jìn)程必須保證非守護(hù)線程都運行完畢后才能結(jié)束。
1.守護(hù)線程例1
from threading import Thread import time def sayhi(name): time.sleep(2) print("%s say hello" %name) if __name__ == "__main__": t=Thread(target=sayhi,args=("egon",)) t.setDaemon(True) #必須在t.start()之前設(shè)置 t.start() print("主線程") print(t.is_alive()) """ 主線程 True """
2.守護(hù)線程例2
from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------")鎖
同步鎖
1.多個線程搶占資源的情況
from threading import Thread import os,time def work(): global n temp=n time.sleep(0.1) n=temp-1 if __name__ == "__main__": n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #結(jié)果可能為99
2.解決方法:
import threading R=threading.Lock() R.acquire() """ 對公共數(shù)據(jù)的操作 """ R.release()
3.同步鎖的引用
from threading import Thread,Lock import os,time def work(): global n lock.acquire() temp=n print("子進(jìn)程temp", temp) time.sleep(0.1) n=temp-1 print("子進(jìn)程n", n) lock.release() if __name__ == "__main__": lock=Lock() n=5 l=[] for i in range(5): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n)
3.互斥鎖與join的區(qū)別
#不加鎖:并發(fā)執(zhí)行,速度快,數(shù)據(jù)不安全 from threading import current_thread,Thread,Lock import os,time def task(): global n print("%s is running" %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == "__main__": n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print("主:%s n:%s" %(stop_time-start_time,n)) """ Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:0.5216062068939209 n:99 """ #不加鎖:未加鎖部分并發(fā)執(zhí)行,加鎖部分串行執(zhí)行,速度慢,數(shù)據(jù)安全 from threading import current_thread,Thread,Lock import os,time def task(): #未加鎖的代碼并發(fā)運行 time.sleep(3) print("%s start to run" %current_thread().getName()) global n #加鎖的代碼串行運行 lock.acquire() temp=n time.sleep(0.5) n=temp-1 lock.release() if __name__ == "__main__": n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print("主:%s n:%s" %(stop_time-start_time,n)) """ Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:53.294203758239746 n:0 """ #有的同學(xué)可能有疑問:既然加鎖會讓運行變成串行,那么我在start之后立即使用join,就不用加鎖了啊,也是串行的效果啊 #沒錯:在start之后立刻使用jion,肯定會將100個任務(wù)的執(zhí)行變成串行,毫無疑問,最終n的結(jié)果也肯定是0,是安全的,但問題是 #start后立即join:任務(wù)內(nèi)的所有代碼都是串行執(zhí)行的,而加鎖,只是加鎖的部分即修改共享數(shù)據(jù)的部分是串行的 #單從保證數(shù)據(jù)安全方面,二者都可以實現(xiàn),但很明顯是加鎖的效率更高. from threading import current_thread,Thread,Lock import os,time def task(): time.sleep(3) print("%s start to run" %current_thread().getName()) global n temp=n time.sleep(0.5) n=temp-1 if __name__ == "__main__": n=100 lock=Lock() start_time=time.time() for i in range(100): t=Thread(target=task) t.start() t.join() stop_time=time.time() print("主:%s n:%s" %(stop_time-start_time,n)) """ Thread-1 start to run Thread-2 start to run ...... Thread-100 start to run 主:350.6937336921692 n:0 #耗時是多么的恐怖
4.死鎖與遞歸鎖
進(jìn)程也有死鎖與遞歸鎖
所謂死鎖: 是指兩個或兩個以上的進(jìn)程或線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程,如下就是死鎖
import time from threading import Lock,Thread noodle = 100 fork = 100 noodle_lock = Lock() fork_lock = Lock() def eat1(name): global noodle,fork noodle_lock.acquire() print("%s拿到面了" % name) fork_lock.acquire() print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) fork_lock.release() print("%s放下叉子了" % name) noodle_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork fork_lock.acquire() print("%s拿到叉子了"%name) noodle_lock.acquire() print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) noodle_lock.release() print("%s放下面"%name) fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()
解決方法,遞歸鎖,在Python中為了支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock。
這個RLock內(nèi)部維護(hù)著一個Lock和一個counter變量,counter記錄了acquire的次數(shù),從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發(fā)生死鎖:
import time from threading import Thread,RLock noodle = 100 fork = 100 noodle_lock = fork_lock = RLock() def eat1(name): global noodle,fork noodle_lock.acquire() print("%s拿到面了" % name) fork_lock.acquire() print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) fork_lock.release() print("%s放下叉子了" % name) noodle_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork fork_lock.acquire() print("%s拿到叉子了"%name) noodle_lock.acquire() print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) noodle_lock.release() print("%s放下面"%name) fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()
使用互斥鎖解決死鎖問題:
import time from threading import Thread,Lock noodle = 100 fork = 100 noodle_fork_lock = Lock() def eat1(name): global noodle,fork noodle_fork_lock.acquire() print("%s拿到面了" % name) print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) print("%s放下叉子了" % name) noodle_fork_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork noodle_fork_lock.acquire() print("%s拿到叉子了"%name) print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) print("%s放下面"%name) noodle_fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()信號量
同進(jìn)程的一樣
Semaphore管理一個內(nèi)置的計數(shù)器,
每當(dāng)調(diào)用acquire()時內(nèi)置計數(shù)器-1;
調(diào)用release() 時內(nèi)置計數(shù)器+1;
計數(shù)器不能小于0;當(dāng)計數(shù)器為0時,acquire()將阻塞線程直到其他線程調(diào)用release()。
實例:(同時只有5個線程可以獲得semaphore,即可以限制最大連接數(shù)為5):
from threading import Thread,Semaphore import threading import time def func(): sm.acquire() print("%s get sm" %threading.current_thread().getName()) time.sleep(3) sm.release() if __name__ == "__main__": sm=Semaphore(5) for i in range(23): t=Thread(target=func) t.start()定時器
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed線程隊列
queue隊列 :使用import queue,用法與進(jìn)程Queue一樣
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
1.先進(jìn)先出
import queue # q = queue.Queue() 先進(jìn)先出 # 在線程之間數(shù)據(jù)安全,自帶線程鎖的數(shù)據(jù)容器 lq = queue.LifoQueue() # 棧 先進(jìn)后出 算法和數(shù)據(jù)結(jié)構(gòu)中 lq.put(1) lq.put(2) lq.put(3) print(lq.get()) print(lq.get()) print(lq.get()) # print(lq.get())#如果隊列里邊沒有值了,進(jìn)行g(shù)et操作,會堵塞
優(yōu)先級隊列
import queue pq = queue.PriorityQueue() # 優(yōu)先級隊列 pq.put(3) pq.put(5) pq.put(2) print(pq.get()) print(pq.get()) print(pq.get()) pq.put("c") pq.put("a") pq.put("A") print(pq.get()) print(pq.get()) print(pq.get()) pq.put((10,"asfghfgk")) pq.put((20,"2iyfhejcn")) pq.put((15,"qwuriyhf")) print(pq.get()) print(pq.get()) print(pq.get())Python標(biāo)準(zhǔn)模塊--concurrent.futures
https://docs.python.org/dev/l...
#1 介紹 concurrent.futures模塊提供了高度封裝的異步調(diào)用接口 ThreadPoolExecutor:線程池,提供異步調(diào)用 ProcessPoolExecutor: 進(jìn)程池,提供異步調(diào)用 Both implement the same interface, which is defined by the abstract Executor class. #2 基本方法 #submit(fn, *args, **kwargs) 異步提交任務(wù) #map(func, *iterables, timeout=None, chunksize=1) 取代for循環(huán)submit的操作 #shutdown(wait=True) 相當(dāng)于進(jìn)程池的pool.close()+pool.join()操作 wait=True,等待池內(nèi)所有任務(wù)執(zhí)行完畢回收完資源后才繼續(xù) wait=False,立即返回,并不會等待池內(nèi)的任務(wù)執(zhí)行完畢 但不管wait參數(shù)為何值,整個程序都會等到所有任務(wù)執(zhí)行完畢 submit和map必須在shutdown之前 #result(timeout=None) 取得結(jié)果 #add_done_callback(fn) 回調(diào)函數(shù)
import os import time import random from threading import get_ident from concurrent.futures import ThreadPoolExecutor t_pool = ThreadPoolExecutor(os.cpu_count()) def func(i): time.sleep(random.randint(1,2)) print("線程:{name},任務(wù){(diào)i}".format(name=get_ident(),i=i)) return "*"*i def call_bak(ret): print("線程:{name},返回值長度:{i}".format(name=get_ident(),i=len(ret.result()))) print("主線程:{name}".format(name=get_ident())) for i in range(1,20): t_pool.submit(func,i).add_done_callback(call_bak)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42170.html
摘要:進(jìn)程可創(chuàng)建多個線程來執(zhí)行同一程序的不同部分。就緒等待線程調(diào)度。運行線程正常運行阻塞暫停運行,解除阻塞后進(jìn)入狀態(tài)重新等待調(diào)度。消亡線程方法執(zhí)行完畢返回或者異常終止。多線程多的情況下,依次執(zhí)行各線程的方法,前頭一個結(jié)束了才能執(zhí)行后面一個。 淺談Python多線程 作者簡介: 姓名:黃志成(小黃)博客: 博客 線程 一.什么是線程? 操作系統(tǒng)原理相關(guān)的書,基本都會提到一句很經(jīng)典的話: 進(jìn)程...
摘要:上一篇文章進(jìn)程專題完結(jié)篇多進(jìn)程處理的一般建議下一篇文章線程專題多線程使用的必要性進(jìn)程線程進(jìn)程能夠完成多任務(wù),比如在一個電腦上可以運行多個軟件。由于占用資源少,也使得多線程程序并發(fā)比較高。 上一篇文章:Python進(jìn)程專題完結(jié)篇:多進(jìn)程處理的一般建議下一篇文章:Python線程專題1:多線程使用的必要性 進(jìn)程VS線程 進(jìn)程:能夠完成多任務(wù),比如在一個電腦上可以運行多個軟件。線程:也能夠...
摘要:其次,解釋器的主循環(huán),一個名為的函數(shù),讀取字節(jié)碼并逐個執(zhí)行其中的指令。所有線程都運行相同的代碼,并以相同的方式定期從它們獲取鎖定。無論如何,其他線程無法并行運行。 概述 如今我也是使用Python寫代碼好多年了,但是我卻很少關(guān)心GIL的內(nèi)部機制,導(dǎo)致在寫Python多線程程序的時候。今天我們就來看看CPython的源代碼,探索一下GIL的源碼,了解為什么Python里要存在這個GIL,...
摘要:也提供多線程支持,而且中的線程并非是模擬出來的多線程,而是系統(tǒng)級別的標(biāo)準(zhǔn)庫提供了兩個模塊和。同一個變量,線程則會互相共享。例如多個線程對銀行中的某一個賬戶進(jìn)行操作。但是實際情況是隨意切換線程。說到的多線程編程,就會繞不過。 該文章參考了http://www.liaoxuefeng.com/wi... 廖雪峰的教程。 一個進(jìn)程至少有一個線程。Python也提供多線程支持,而且Python...
摘要:如下面的例子,在學(xué)習(xí)線程時,將文件名命名為腳本完全正常沒問題,結(jié)果報下面的錯誤。最大的問題就是的多線程程序并不能利用多核的優(yōu)勢比如一個使用了多個線程的計算密集型程序只會在一個單上面運行。 本文記錄學(xué)習(xí)Python遇到的問題和一些常用用法,注本開發(fā)環(huán)境的Python版本為2.7。 一、python文件命名 在python文件命名時,一定要注意不能和系統(tǒng)默認(rèn)的模塊名沖突,否則會報錯。如下面...
閱讀 1789·2021-11-25 09:43
閱讀 15430·2021-09-22 15:11
閱讀 2637·2019-08-30 13:19
閱讀 2019·2019-08-30 12:54
閱讀 1822·2019-08-29 13:06
閱讀 933·2019-08-26 14:07
閱讀 1622·2019-08-26 10:47
閱讀 3043·2019-08-26 10:41