今日要整理的內容有html
1. 操做系統中線程理論python
2.python中的GIL鎖程序員
3.線程在python中的使用算法
開始今日份整理編程
1. 操做系統中線程理論小程序
1.1 線程引入背景安全
以前咱們已經瞭解了操做系統中進程的概念,程序並不能單獨運行,只有將程序裝載到內存中,系統爲它分配資源才能運行,而這種執行的程序就稱之爲進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。在多道編程中,咱們容許多個程序同時加載到內存中,在操做系統的調度下,能夠實現併發地執行。這是這樣的設計,大大提升了CPU的利用率。進程的出現讓每一個用戶感受到本身獨享CPU,所以,進程就是爲了在CPU上實現多道編程而提出的。服務器
那麼有了進程爲何還須要線程多線程
進程有不少優勢,它提供了多道編程,讓咱們感受咱們每一個人都擁有本身的CPU和其餘資源,能夠提升計算機的利用率。不少人就不理解了,既然進程這麼優秀,爲何還要線程呢?其實,仔細觀察就會發現進程仍是有不少缺陷的,主要體如今兩點上:併發
進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。
進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行。
若是這兩個缺點理解比較困難的話,舉個現實的例子也許你就清楚了:若是把咱們上課的過程當作一個進程的話,那麼咱們要作的是耳朵聽老師講課,手上還要記筆記,腦子還要思考問題,這樣才能高效的完成聽課的任務。而若是隻提供進程這個機制的話,上面這三件事將不能同時執行,同一時間只能作一件事,聽的時候就不能記筆記,也不能用腦子思考,這是其一;若是老師在黑板上寫演算過程,咱們開始記筆記,而老師忽然有一步推不下去了,阻塞住了,他在那邊思考着,而咱們呢,也不能幹其餘事,即便你想趁此時思考一下剛纔沒聽懂的一個問題都不行,這是其二。
如今你應該明白了進程的缺陷了,而解決的辦法很簡單,咱們徹底可讓聽、寫、思三個獨立的過程,並行起來,這樣很明顯能夠提升聽課的效率。而實際的操做系統中,也一樣引入了這種相似的機制——線程。
注意:進程是資源分配的最小單位,線程是cpu調度的最小單位,每個進程中至少一個線程
1.2 進程與線程的區別
進程與線程之間的區別主要是有如下幾點
1.3 線程的特色
在多線程的操做系統中,一般是在一個進程中包括多個線程,每一個線程都是做爲利用CPU的基本單位,是花費最小開銷的實體。線程具備如下屬性。
補充TCB的內容
TCB包括如下信息: (1)線程狀態。 (2)當線程不運行時,被保存的現場資源。 (3)一組執行堆棧。 (4)存放每一個線程的局部變量主存區。 (5)訪問同一個進程中的主存和其它資源。 用於指示被執行指令序列的程序計數器、保留局部變量、少數狀態參數和返回地址等的一組寄存器和堆棧。
1.4 使用線程的使用場景
開啓一個字處理軟件進程,該進程確定須要辦不止一件事情,好比監聽鍵盤輸入,處理文字,定時自動將文字保存到硬盤,這三個任務操做的都是同一塊數據,於是不能用多進程。只能在一個進程裏併發地開啓三個線程,若是是單線程,那就只能是,鍵盤輸入時,不能處理文字和自動保存,自動保存時又不能輸入和處理文字。
1.5 內存中的線程
多個線程共享同一個進程的地址空間中的資源,是對一臺計算機上多個進程的模擬,有時也稱線程爲輕量級的進程。
而對一臺計算機上多個進程,則共享物理內存、磁盤、打印機等其餘物理資源。多線程的運行也多進程的運行相似,是cpu在多個線程之間的快速切換。
不一樣的進程之間是充滿敵意的,彼此是搶佔、競爭cpu的關係,若是迅雷會和QQ搶資源。而同一個進程是由一個程序員的程序建立,因此同一進程內的線程是合做關係,一個線程能夠訪問另一個線程的內存地址,你們都是共享的,一個線程乾死了另一個線程的內存,那純屬程序員腦子有問題。
相似於進程,每一個線程也有本身的堆棧,不一樣於進程,線程庫沒法利用時鐘中斷強制線程讓出CPU,能夠調用thread_yield運行線程自動放棄cpu,讓另一個線程運行。
線程一般是有益的,可是帶來了不小程序設計難度,線程的問題是:
1. 父進程有多個線程,那麼開啓的子線程是否須要一樣多的線程
2. 在同一個進程中,若是一個線程關閉了文件,而另一個線程正準備往該文件內寫內容呢?
所以,在多線程的代碼中,須要更多的心思來設計程序的邏輯、保護程序的數據。
2.python中的GIL鎖
2.1 python如何使用GIL鎖
全局解釋器鎖GIL
Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到要在主循環中,同時只有一個線程在執行。雖然 Python 解釋器中能夠「運行」多個線程,但在任意時刻只有一個線程在解釋器中運行。
對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。
在多線程環境中,Python 虛擬機按如下方式執行:
a、設置 GIL;
b、切換到一個線程去運行;
c、運行指定數量的字節碼指令或者線程主動讓出控制(能夠調用 time.sleep(0));
d、把線程設置爲睡眠狀態;
e、解鎖 GIL;
d、再次重複以上全部步驟。
在調用外部代碼(如 C/C++擴展函數)的時候,GIL將會被鎖定,直到這個函數結束爲止(因爲在這期間沒有Python的字節碼被運行,因此不會作線程切換)編寫擴展的程序員能夠主動解鎖GIL。
python中線程不能同時訪問cpu的確是一個缺點,不過咱們在寫代碼的時候能夠判斷這個代碼塊是多I/O仍是多計算,多I/O即便是單cpu也是能夠順暢執行的,不過對於高計算的則開啓多進程而後進程中開啓多線程併發的執行計算,對於python還有一個緣由是他是解釋性語言,在編譯的同時不知道下面的代碼的具體執行狀況,不能多cpu同時調用。
3.線程在python中的使用
3.1線程的倆種方式
方式一:
from threading import Thread import time import os def func(i): time.sleep(1) print('子進程%s!'%i,os.getpid()) for i in range(10): t = Thread(target= func,args=(i,)) t.start() print('主進程!',os.getpid())
方式二:
#自建類的方式 from threading import Thread import time import os class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self):#和進程同樣,自建類,必須用run函數 time.sleep(1) print('子進程%s'%self.name,os.getpid()) if __name__ == '__main__': print('主線程!',os.getpid()) for i in range(10): p = MyThread(i) p.start()
執行代碼咱們會發現,肉眼所看基本上全部的線程都是一塊兒創建成功,這個時候就會有疑問,不是有GIL全局解釋鎖嗎,不該該每隔一秒才建立一個線程嗎,看圖
步驟以下
注意:程序中的IO操做是不佔用全局解釋器鎖和CPU的
3.2 threading內的其餘用法
3.2.1 join方法
線程中join方法和進程中的join方法時一致的,都是對主線程的阻塞,直到子線程的中的代碼執行結束
看代碼
from threading import Thread import time def func(i): time.sleep(1) print('子線程%s'%i) t_list =[] for i in range(10): t = Thread(target= func,args=(i,)) t_list.append(t) t.start() for t in t_list: t.join() print('全部線程已經所有執行完畢!')
3.2.2 守護線程
守護線程和守護進程是差很少的,只是開啓的方式一個是參數一個是內置方法
from threading import Thread import time def main(): print('主線程開始!') time.sleep(3) print('主線程結束') def func(): print('子線程開始') time.sleep(5) print('子線程結束') def daemon(): while True: time.sleep(1) print('我活的很好!') t = Thread(target= daemon,) t.setDaemon(True)#設定爲守護線程的方法 t.start()
t1 = Thread(target= main,)
t2 = Thread(target= func,)
t1.start()
t2.start()
看結果會發現,守護線程不緊守護了主線程,同時守護了子線程,能夠得出如下結果
守護線程和守護進程不一樣,守護線程會守護主線程直到主線程結束,若是這個時候主線程要等待子線程的執行結束,那麼守護線程同時對子線程進行守護
3.2.3 數據共享
同一個進程內的線程因爲是共享一個資源空間,因此一個進程內的線程是天生共享資源,看代碼
# from threading import Thread # # n = 100 # def func(): # global n # n-=1 # # t_list =[] # for i in range(100): # t = Thread(target= func,) # t_list.append(t) # t.start() # for i in t_list: # i.join() # print(n)
3.2.4 查看線程id
倆種方式
方式一:
#方式一:面向對象的方式 from threading import Thread import os class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('子進程%s'%self.name,self.ident,os.getpid()) for i in range(10): t = MyThread(i) t.start()
方式二:
#方式二:普通方式 from threading import Thread,currentThread import os def func(i): print('子進程%s'%i,currentThread().ident,os.getpid()) for i in range(10): t = Thread(target= func,args= (i,)) t.start()
3.2.5 threading的其餘方法
from threading import Thread,currentThread,enumerate,active_count import os import time def func(i): time.sleep(1) print('子進程%s'%i,currentThread().ident,os.getpid()) for i in range(10): t = Thread(target= func,args= (i,)) t.start() print(enumerate())#枚舉查看活着的線程 print(active_count())#查看活着的線程數量,子線程數量加上主線程
3.3 線程中的鎖
3.3.1 互斥鎖
雖然說一個進程內線程都是共享資源,,可是實際中咱們仍是在線程中若是有+=、-=、*=、/=等操做仍是要加鎖,先看例子,爲何會這樣
from threading import Thread import time n = 0 def func(): global n count = n time.sleep(0.1)#用阻塞模擬時間片輪轉 n = count+1 t_list =[] for i in range(100): t = Thread(target= func) t_list.append(t) t.start() for i in t_list: i.join() print(n) #結果爲1 #若是將阻塞的代碼註釋後就會變成100 #對於操做系統來講,必定數量的線程並不會出現問題,不過在實際使用中爲了安全性,建議不要作,須要加鎖 #修改後的代碼 from threading import Thread,Lock n = 0 def func(lock):#加鎖操做 global n with lock: n +=1 t_list =[] lock = Lock() for i in range(100): t = Thread(target= func,args=(lock,)) t_list.append(t) t.start() for i in t_list: i.join() print(n) #結果:100 #這楊無論如何,結果都是100
結論:
3.3.2 迭代鎖
3.3.2.1 死鎖問題
要說迭代鎖首先要說死鎖問題,而說明死鎖問題就要說一下著名的科學家吃麪問題
科學家吃麪問題:四個科學家吃麪,桌子上只有一盤面和一把叉子,只有在同時拿到面和叉子,才能夠遲到,拿到面或者叉子是不能作任何事情,下面模擬下科學家吃麪這個問題,代碼以下
#死鎖版
from threading import Thread,Lock import time nooodle_lock = Lock() fork_lock = Lock() def eat1(name): nooodle_lock.acquire() print('%s 拿到了麪條!'%name) fork_lock.acquire() print('%s 拿到了叉子!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 fork_lock.release() print('%s 放下了叉子!' % name) nooodle_lock.release() print('%s 放下了麪條'% name) def eat2(name): fork_lock.acquire() print('%s 拿到了叉子!' % name) nooodle_lock.acquire() print('%s 拿到了麪條!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 nooodle_lock.release() print('%s 放下了麪條'% name) fork_lock.release() print('%s 放下了叉子!' % name) t1 = Thread(target= eat1,args=('飯桶1',)) t2 = Thread(target= eat2,args=('飯桶2',)) t3 = Thread(target= eat1,args=('飯桶3',)) t4 = Thread(target= eat2,args=('飯桶4',)) t1.start() t2.start() t3.start() t4.start() #結果 飯桶1 拿到了麪條! 飯桶1 拿到了叉子! 飯桶1 開始吃麪 飯桶1 放下了叉子! 飯桶2 拿到了叉子! 飯桶1 放下了麪條 飯桶3 拿到了麪條! #飯桶2 和飯桶3 各拿麪條和叉子互不放棄,這樣就形成了死鎖
3.3.2.2 迭代鎖
根據上面的例子,咱們就須要引入一個迭代鎖的概念
正常的互斥鎖,就至關於一個門,一把鑰匙,誰搶到誰進去,如圖
而迭代鎖就是一個門,但是門口掛着一串鑰匙,拿到這一串鑰匙的人就能夠進入門後面的門,如圖
把上面的科學家吃麪問題解決一下
#迭代鎖版
from threading import Thread,RLock import time nooodle_lock =fork_lock= RLock() def eat1(name): nooodle_lock.acquire() print('%s 拿到了麪條!'%name) fork_lock.acquire() print('%s 拿到了叉子!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 fork_lock.release() print('%s 放下了叉子!' % name) nooodle_lock.release() print('%s 放下了麪條'% name) def eat2(name): fork_lock.acquire() print('%s 拿到了叉子!' % name) nooodle_lock.acquire() print('%s 拿到了麪條!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 nooodle_lock.release() print('%s 放下了麪條'% name) fork_lock.release() print('%s 放下了叉子!' % name) t1 = Thread(target= eat1,args=('飯桶1',)) t2 = Thread(target= eat2,args=('飯桶2',)) t3 = Thread(target= eat1,args=('飯桶3',)) t4 = Thread(target= eat2,args=('飯桶4',)) t1.start() t2.start() t3.start() t4.start() #結果就是每個人均可以吃到面了,只是Lock改成RLock
總結:
那麼按照這個思路,咱們能夠改變一下上面的科學家吃麪問題
#最終版
from threading import Thread,Lock import time lock = Lock() def eat1(name): lock.acquire() print('%s 拿到了麪條!'%name) print('%s 拿到了叉子!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 print('%s 放下了叉子!' % name) print('%s 放下了麪條'% name) lock.release() def eat2(name): lock.acquire() print('%s 拿到了叉子!' % name) print('%s 拿到了麪條!' % name) print('%s 開始吃麪'%name) time.sleep(1)#模擬吃麪 print('%s 放下了麪條'% name) print('%s 放下了叉子!' % name) lock.release() Thread(target= eat1,args=('飯桶1',)).start() Thread(target= eat2,args=('飯桶2',)).start() Thread(target= eat1,args=('飯桶3',)).start() Thread(target= eat2,args=('飯桶4',)).start()
最終結論:使用迭代鎖的時候,絕大多數是由於代碼中各類各樣的鎖太多,機制混亂,在一頭霧水的時候能夠先用帶帶鎖解決問題,而後在想着一點點的去解決問題!
小練習:基於多線程的socket的套接字,簡易版
#server端 import socket from threading import Thread sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() def talk(conn,addr): print(addr) while True: conn.send(b'hello world!') while True: conn,addr = sk.accept() Thread(target=talk,args=(conn,addr)).start()#將具體聊天的過程交給不一樣的線程去執行 #客戶端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500)) while True: msg = sk.recv(1024) print(msg) sk.close() #最後的結果就是服務器端能夠接收多個客戶端鏈接,每一個客戶端能夠不受影響的去接收
3.4 線程隊列
首先說一下,一個進程中的多線程共用一塊資源空間,爲何還須要用到隊列,主要是有倆個方面
不一樣於多進程中的有專門的隊列模塊,線程中使用隊列就須要調用python中自帶模塊queue模塊
3.4.1 線程隊列中經常使用的方法
#線程隊列中的經常使用方法 1.隊列的實例化: q = queue.Queue(num)#num能夠爲空,加上則設定隊列的大小 2.隊列的放入以及取出: q.put() q.get() #他倆都是阻塞事件 3.隊列狀態的判斷 q.size() q.empty() q.full() 在多進程中狀態常常有誤,不多去使用 4.隊列的快速取出以及快速放入 import queue q = queue.Queue(1) q.put(1) try: print(q.put_nowait('abc'))#快速放入,無論隊列是都滿,若是滿則丟棄,因此常常不用,隊列滿會報queue.Full錯誤 except queue.Full: pass try: print(q.get_nowait())#快速取出,無論隊列是否空,空則報錯queue.Empty except queue.Empty: pass
3.4.2 算法中的經常使用數據類型
在算法中有如下經常使用數據類型
3.4.3 其餘補充隊列
第一種:棧,後進先出
import queue q = queue.LifoQueue()#棧 q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) #結果 3 2 1
第二種:優先級隊列,put的時候是一個元祖,按照元祖第一個元素的ASCII碼的位置
import queue q = queue.PriorityQueue() q.put((2,'abc')) q.put((1,'bbb')) q.put((3,'ccc')) print(q.get()) print(q.get()) print(q.get()) #結果 (1, 'bbb') (2, 'abc') (3, 'ccc')
3.5 線程池
線程在最開始建立的時候是沒有線程池的概念的,因此線程與進程不一樣的是沒有專門的線程池的方法,須要調用其餘模塊
Python標準模塊--concurrent.futures,官方參考介紹文章
https://docs.python.org/dev/library/concurrent.futures.html
concurrent.futures模塊經常使用方法
#1 介紹 concurrent.futures模塊提供了高度封裝的異步調用接口 ThreadPoolExecutor:線程池,提供異步調用 ProcessPoolExecutor: 進程池,提供異步調用 Both implement the same interface, which is defined by the abstract Executor class. #2 基本方法 #submit(fn, *args, **kwargs) 異步提交任務 #map(func, *iterables, timeout=None, chunksize=1) 取代for循環submit的操做 #shutdown(wait=True) 至關於進程池的pool.close()+pool.join()操做 wait=True,等待池內全部任務執行完畢回收完資源後才繼續 wait=False,當即返回,並不會等待池內的任務執行完畢 但無論wait參數爲什麼值,整個程序都會等到全部任務執行完畢 submit和map必須在shutdown以前 #result(timeout=None) 取得結果 #add_done_callback(fn) 回調函數
3.5.1 線程模塊
#線程模塊 #1.普通建池 from threading import currentThread from concurrent.futures import ThreadPoolExecutor import time def func(i): time.sleep(1) print('子線程%s'%i,currentThread().ident)#查看線程名,以及線程id tp = ThreadPoolExecutor(4)#建池 for i in range(20): tp.submit(func,i)#就至關於進程池中的apply_async異步的提交任務 tp.shutdown()#至關於進程池中的.close()與.join() #2.map建池 from threading import currentThread from concurrent.futures import ThreadPoolExecutor import time def func(i): time.sleep(1) print('子線程%s'%i,currentThread().ident)#查看線程名,以及線程id tp = ThreadPoolExecutor(4)#建池 tp.map(func,range(20)) #3.獲取返回值 from threading import currentThread from concurrent.futures import ThreadPoolExecutor import time def func(i): time.sleep(1) print('子線程%s'%i,currentThread().ident)#查看線程名,以及線程id return i**2 tp = ThreadPoolExecutor(4)#建池 ret_l =[] for i in range(20): ret = tp.submit(func,i)#就至關於進程池中的apply_async異步的提交任務 ret_l.append(ret) tp.shutdown()#至關於進程池中的.close()與.join() for i in ret_l: print(i.result())#就至關於進程中的結果的get() #4.回調函數 from threading import currentThread from concurrent.futures import ThreadPoolExecutor import time def func(i): time.sleep(1) print('子線程%s'%i,currentThread().ident)#查看線程名,以及線程id return i**2 def call_back(ret): print(ret.result())#ret只是獲取到結果的對象,須要對對象進行result() tp = ThreadPoolExecutor(4)#建池 for i in range(20): tp.submit(func,i).add_done_callback(call_back)#就至關於進程池中的apply_async異步的提交任務,和進程池中的callback=函數名一致 tp.shutdown()#至關於進程池中的.close()與.join()
3.5.2 進程模塊
#進程模塊 #1.普通建池 from concurrent.futures import ProcessPoolExecutor import os import time def func(i): time.sleep(1) print('子進程%s'%i,os.getpid()) if __name__ == '__main__': pp= ProcessPoolExecutor(4) for i in range(20): pp.submit(func,i) pp.shutdown() #2.map建池 from concurrent.futures import ProcessPoolExecutor import os import time def func(i): time.sleep(1) print('子進程%s'%i,os.getpid()) if __name__ == '__main__': pp= ProcessPoolExecutor(4) pp.map(func,range(20)) #3.返回值 from concurrent.futures import ProcessPoolExecutor import os import time def func(i): time.sleep(1) print('子進程%s'%i,os.getpid()) return i**2 if __name__ == '__main__': pp= ProcessPoolExecutor(4) ret_l =[] for i in range(20): ret = pp.submit(func,i) ret_l.append(ret) pp.shutdown() for ret in ret_l: print(ret.result()) #4.回調函數 from concurrent.futures import ProcessPoolExecutor import os import time def func(i): time.sleep(1) print('子進程%s'%i,os.getpid()) return i**2 def call_back(ret): print(ret.result()) if __name__ == '__main__': pp= ProcessPoolExecutor(4) for i in range(20): pp.submit(func,i).add_done_callback(call_back) pp.shutdown()
最後:後期咱們建池的話主要使用concurrent.futures,一是比較新,二是既可建線程池也能夠創建進程池,方便使用!
總結: