TCP服務端實現併發
1.將不一樣的功能儘可能拆分紅不一樣的函數
拆分出來的功能能夠被多個地方使用
2.將鏈接循環和通訊循環拆分紅不一樣的函數
3.將通訊循環作成多線程html
import socket from threading import Thread import time """ 服務端 1.要有固定的IP和PORT 2.24小時不間斷提供服務 3.可以支持併發 """ server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0:break print(data.decode('utf-8')) time.sleep(10) #開啓一個客戶端的時候10s打印一個結果,多個客戶端就會有併發效果,開10個感受上10s打印10個 conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() while True: conn, addr = server.accept() # 監聽 等待客戶端的鏈接 阻塞態 print(addr) t = Thread(target=talk,args=(conn,)) t.start()
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello') data = client.recv(1024) print(data.decode('utf-8'))
1. 只有python有GIL嗎?python
GIL全局解釋器鎖目前是全部解釋型語言的通病不可調和,必須加鎖,編譯型語言在編譯的時候已經管理好了多線程運行的問題
有人給處理的數據加鎖,雖然能夠,可是那樣就會有不少鎖,出現大量的串行,給解釋器加鎖雖然也是串行,可是至少還能夠實現併發
web
2. GIL全局解釋器鎖的存在是python的問題麼?
GIL並非Python的特性,Python徹底能夠不依賴於GIL,只是Cpython的內存管理不是線程安全,就引入這一個概念,JPython就不存在安全
內存管理(垃圾回收機制)
引用計數: 值與變量的綁定關係的個數
標記清除: 當內存快要滿的時候 會自動中止程序的運行 檢測全部的變量與值的綁定關係
給沒有綁定關係的值打上標記,最後一次性清除
分代回收: (垃圾回收機制也是須要消耗資源的,而正常一個程序的運行內部會使用到不少變量與值
而且有一部分相似於常量,減小垃圾回收消耗的時間,應該對變量與值的綁定關係作一個分類)
新生代(5S)》》》青春代(10s)》》》老年代(20s)
垃圾回收機制掃描必定次數發現關係還在,會將該對關係移至下一代
隨着代數的遞增 掃描頻率是下降的多線程
3. 做用原理:併發
將併發運行變成串行,犧牲效率來提升數據的安全(全部互斥鎖的本質)
控制同一時間內共享數據只能被一個線程所修改(不能並行可是可以實現併發)app
4.緣由詳解
一個進程中必帶一個解釋器和一個垃圾回收線程
一個進程下的多個線程都須要運行,就必須去調解釋器,垃圾回收線程也要用解釋器,
若是不加限制,若是回收機制和其餘線程同時使用解釋器,會同時執行,回收機制就可能誤刪掉那些剛建立還沒來得及綁定的變量資源
所以必須給解釋器加鎖,只能容許一個線程使用,我幹活的時候你滾一邊去,不要干擾我,這樣纔不會衝突
進程能夠利用多核,可是開銷大,python的多線程開銷小,但卻沒法利用多核優點,難道說說python多線程沒用了?dom
同一個進程下的多個線程雖不能實現並行,可是可以實現併發
多個進程下的線程可以實現並行,發揮多核優點socket
對計算來講,cpu越多越好,可是對於I/O來講,再多的cpu也沒用ide
固然對運行一個程序來講,隨着cpu的增多執行效率確定會有所提升(無論提升幅度多大,總會有所提升)
這是由於一個程序基本上不會是純計算或者純I/O
因此咱們只能相對的去看一個程序究竟是計算密集型仍是I/O密集型,從而進一步分析python的多線程到底有無用武之地
#分析: 咱們有四個任務須要處理,處理方式確定是要玩出併發的效果,解決方案能夠是: 方案一:開啓四個進程 方案二:一個進程下,開啓四個線程 #單核狀況下:
若是四個任務是計算密集型,沒有多核來並行計算,方案一徒增了建立進程的開銷,開線程牛逼 若是四個任務是I/O密集型,建立進程的開銷大,且進程的切換速度遠不如線程,開線程牛逼 #多核狀況下:
若是四個任務是計算密集型,多核意味着並行計算,在python中一個進程中同一時刻只有一個線程執行用不上多核,開進程牛逼 若是四個任務是I/O密集型,再多的核去開再多進程仍是要一個個等IO的時間,也解決不了I/O問題,多線程節約了資源還交互進行提升效率,開線程牛逼
目前計算機都是多核,因此:
計算密集型:多線程還不如串行(沒有大量切換),多核多進程牛逼
IO密集型: 多線程明顯提升效率,多核沒屌用
多線程和多進程都有本身的優勢,要根據項目需求合理選擇
目前大多數軟件都是IO密集型:socket 爬蟲 web
也有計算密集型的:金融分析
# IO密集型 from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) #睡覺也是典型的IO操做 if __name__ == '__main__': l=[] print(os.cpu_count()) #獲取本機cpu數量 start=time.time() for i in range(300): # p=Process(target=work) #用進程跑 耗時12.69s p=Thread(target=work) #用線程跑 耗時2.03s l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
# 計算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本機爲4核 start=time.time() for i in range(8): p=Process(target=work) #耗時24.21s # p=Thread(target=work) #耗時43.61s l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
對於不一樣的數據,要想保證安全,須要加不一樣的鎖處理
GIL並不能保證數據的安全,它是對Cpython解釋器加鎖,針對的是線程
保證的是同一個進程下多個線程之間的安全
#GIL內置存在,只容許一個線程經過,但數據依然不安全
from threading import Thread import time n = 100 def task(): global n tmp = n time.sleep(0.1) # 在睡覺的狀況下,輸出99,1拿到全局鎖遇到IO去睡覺時,GIL必須交出來,2搶到,1還沒運行完,0.1s足夠你們能搶一遍GIL,因此都拿到100去睡覺了 # 不睡覺的狀況下,輸出0,1搶到GIL沒有IO會一直拿着直到輸出後釋放,釋放後2才能搶到GIL才能繼續搞 n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
#加自定義鎖保證數據安全
from threading import Thread,Lock import time n = 100 mutex = Lock() def task(): global n tmp = n #99 放在這裏,tmp受到GIL控制,GIL尚未出計算結果就釋放,0.1s的睡眠時間GIL夠你們搞一遍了,也就是都獲得了tmp=100 mutex.acquire() # tmp = n #0 放在這裏,tmp受到自定義鎖控制,自定義鎖必須在運行結束才能釋放,你們拿到的n都是上次結果-1 time.sleep(0.1) n = tmp -1 mutex.release() t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
進程也有死鎖與遞歸鎖,在進程那裏忘記說了,放到這裏一切說了額
1.只要類加括號實例化對象
不管傳入的參數是否同樣生成的對象確定不同,不信你打印id
單例模式除外
mutexA = Lock() mutexB = Lock()
mutexA 和 mutexB 可不是同樣東西
2.鏈式賦值出來的對象那但是一毛同樣 mutexA = mutexB = RLock() # A B如今是同一把鎖
lock鎖 一次acquire必須對應一次release,不能連續acquire
所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,(你拿了我想要的鎖,我拿了你想要的鎖)
若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程
以下就是死鎖:
from threading import Thread,Lock,current_thread,RLock import time """ 自定義鎖一次acquire必須對應一次release,不能連續acquire 本身千萬不要輕易的處理鎖的問題 """ mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 建立線程自動觸發run方法 run方法內調用func1 func2至關於也是自動觸發 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s搶到了A鎖'%self.name) # self.name等價於current_thread().name mutexB.acquire() print('%s搶到了B鎖'%self.name) mutexB.release() print('%s釋放了B鎖'%self.name) mutexA.release() print('%s釋放了A鎖'%self.name) def func2(self): mutexB.acquire() print('%s搶到了B鎖'%self.name) time.sleep(1) mutexA.acquire() print('%s搶到了A鎖' % self.name) mutexA.release() print('%s釋放了A鎖' % self.name) mutexB.release() print('%s釋放了B鎖' % self.name) for i in range(3): t = MyThread() t.start() ''' Thread-1搶到了A鎖 Thread-1搶到了B鎖 Thread-1釋放了B鎖 Thread-1釋放了A鎖 Thread-1搶到了B鎖 Thread-2搶到了A鎖 #到這裏以後就出現死鎖了,你想要的個人鎖我想要你的鎖 '''
解決死鎖方法,遞歸鎖,在Python中爲了支持在同一線程中屢次請求同一資源,python提供了可重入鎖RLock。
這個RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源能夠被屢次require。直到一個線程全部的acquire都被release,其餘的線程才能得到資源。上面的例子若是使用RLock代替Lock,則不會發生死鎖:
Rlock能夠被第一個搶到鎖的人連續的acquire和release
每acquire一次鎖身上的計數加1
每release一次鎖身上的計數減1
只要鎖的計數不爲0 其餘人都不能搶
from threading import Thread,Lock,current_thread,RLock import time """ Rlock能夠被第一個搶到鎖的人連續的acquire和release 每acquire一次鎖身上的計數加1 每release一次鎖身上的計數減1 只要鎖的計數不爲0 其餘人都不能搶 """ mutexA = mutexB = RLock() # A B如今是同一把鎖,搶鎖以後會有一個計數 搶一次計數加一 針對的是第一個搶到個人人 print(id(mutexB)) #35379080 print(id(mutexA)) #35379080 class MyThread(Thread): def run(self): # 建立線程自動觸發run方法 run方法內調用func1 func2至關於也是自動觸發 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s搶到了A鎖'%self.name) # self.name等價於current_thread().name mutexB.acquire() print('%s搶到了B鎖'%self.name) mutexB.release() print('%s釋放了B鎖'%self.name) mutexA.release() print('%s釋放了A鎖'%self.name) def func2(self): mutexB.acquire() print('%s搶到了B鎖'%self.name) time.sleep(1) mutexA.acquire() print('%s搶到了A鎖' % self.name) mutexA.release() print('%s釋放了A鎖' % self.name) mutexB.release() print('%s釋放了B鎖' % self.name) for i in range(3): t = MyThread() t.start()
信號量可能在不一樣的領域中 對應不一樣的知識點
互斥鎖:一個廁所(一個坑位)
信號量:公共廁所(多個坑位)
from threading import Semaphore,Thread import time import random sm = Semaphore(3) # 造了一個含有五個的坑位的公共廁所 def task(name): sm.acquire() print('%s佔了一個坑位'%name) time.sleep(random.randint(1,30)) print('%s放出一個坑位' % name) sm.release() for i in range(5): t = Thread(target=task,args=(i,)) t.start() ''' 0佔了一個坑位 1佔了一個坑位 2佔了一個坑位 2放出一個坑位 3佔了一個坑位 0放出一個坑位 4佔了一個坑位 1放出一個坑位 3放出一個坑位 4放出一個坑位 '''
from threading import Event,Thread import time # 先生成一個event對象 e = Event() def light(): print('紅燈正亮着') time.sleep(3) e.set() # 發信號 print('綠燈亮了') def car(name): print('%s正在等紅燈'%name) e.wait() # 等待信號 print('%s加油門飆車了'%name) t = Thread(target=light) t.start() for i in range(3): t = Thread(target=car,args=('傘兵%s'%i,)) t.start() ''' 紅燈正亮着 傘兵0正在等紅燈 傘兵1正在等紅燈 傘兵2正在等紅燈 綠燈亮了 傘兵1加油門飆車了 傘兵2加油門飆車了 傘兵0加油門飆車了 '''
同一個進程下的多個線程原本就是數據共享 爲何還要用隊列
由於隊列是管道+鎖 使用隊列你就不須要本身手動操做鎖的問題
由於鎖操做的很差極容易產生死鎖現象
1.正常隊列 先進先出 q = queue.Queue() q.put('hahha') print(q.get()) 2.先進後出 q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) #3 3.自定義優先級 越小越優先 q = queue.PriorityQueue() q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get()) #(-10, 'yyyy')