GIL全域性直譯器
阿新 • • 發佈:2022-01-18
複習
- 殭屍程序
所有的程序在執行結束後並不會立即被銷燬(父程序需要獲取子程序的資源) - 孤兒程序
子程序正在執行但是產生該程序的父程序意外死亡 - 守護程序
守護程序的結束取決於守護的物件的程序何時結束 - 互斥鎖
鎖:將併發變成序列,犧牲了效率但是提高了資料的安全
mutex.erquier() 搶鎖
mutex.release() 釋放鎖
注:鎖雖然使用很方便,但不要輕易使用,容易造成死鎖現象 - 生產者消費者模
確保供需平衡 - 執行緒理論
程序僅僅是一個資源單位,程序中真正乾貨的其實是執行緒(每一個執行緒肯定自帶一個主執行緒)
開設執行緒的消耗遠遠小於開設程序的消耗,同一個程序的多個執行緒資料是共享的 - 執行緒其他特性
幾乎與程序物件方法一致 - 訊息佇列簡介
訊息佇列就是一個存放資料的地方,並且資料遵循先進先出的原則
只要用於解決生產者和消費者供需不平衡的問題
1、GIL全域性直譯器鎖(重要理論)
""" In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces. 1.python直譯器有很多版本(預設的是Cpython) Cpython,、Jpython,pypython 在Cpython中,GIL全域性直譯器鎖,其實也是一把互斥鎖,主要用於阻止同一個程序下的多個執行緒同時被執行(python的多執行緒無法使用多核優勢) GIL肯定存在於Cpython直譯器中,主要原因就在於Cpython直譯器的記憶體管理不是執行緒安全的 2.記憶體管理>>>垃圾回收機制 應用計數 標記清除 分代回收 """ # 1.GIL是Cpython直譯器的特點 # 2.python同一個程序內的多個執行緒無法利用多和優勢(不能並行可以併發) # 3.同一個程序內的多個執行緒要想執行必須想搶GIl鎖 # 4.所有的解釋性語言幾乎都無法實現同一個程序下多個執行緒同時被執行
2、驗證GIl的存在及其功能
from threading import Thread # 呼叫開啟執行緒模組 import time # 呼叫時間模組 m = 100 # 定義變數 def test(): # 定義函式 global m # 宣告更改全域性名稱空間的值 tmp = m # 指向100 # time.sleep(1) 新增這個列印就會變成99,重要 tmp -= 1 # 100 - 1 m = tmp # 重新指向 for i in range(100): t = Thread(target=test) # 迴圈開啟執行緒 t.start() # 啟動執行緒 time.sleep(3) # 停止三秒鐘 print(m) # 列印 # 列印為0
3、死鎖現象
from threading import Thread, Lock
import time
A = Lock()
B = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
A.acquire()
print('%s 搶到了A鎖' % self.name) # current_thread().name 獲取執行緒名稱
B.acquire()
print('%s 搶到了B鎖' % self.name)
time.sleep(1)
B.release()
print('%s 釋放了B鎖' % self.name)
A.release()
print('%s 釋放了A鎖' % self.name)
def func2(self):
B.acquire()
print('%s 搶到了B鎖' % self.name)
A.acquire()
print('%s 搶到了A鎖' % self.name)
A.release()
print('%s 釋放了A鎖' % self.name)
B.release()
print('%s 釋放了B鎖' % self.name)
for i in range(10):
obj = MyThread()
obj.start()
"""就算知道鎖的特性及使用方式 也不要輕易的使用 因為容易產生死鎖現象"""
4、python多執行緒是否沒用
# 是否有用需要看情況而定(程式的型別)
"""
IO密集型
eg:
四個任務,每個任務耗時10s
開設多程序沒有太大的優勢:10s+
遇到IO就需要切換,並且開設程序還需要申請記憶體空間和拷貝程式碼
開設多執行緒有優勢:10s+
不需要消耗額外的資源
計算密集型
eg:四個任務,每個任務耗時10s
開設多程序可以利用多核優勢:10s+
開設多執行緒無法利用多核優勢:10s+
可以多程序結合多執行緒
"""
"""IO密集型"""
from multiprocessing import Process
from threading import Thread
import thrading
import os, time
def work():
time.sleep(2)
if__name__ == "__main__":
l = []
print(os.cpu_count()) # 獲取點前機器的核數
start = time.time()
for i in range(800):
# p = Process(target=work) # 建立程序
p = Thread(target=work) # 建立執行緒
l.append(p)
p.start()
for i in l:
p.join()
stop = time.time()
print("執行時間:%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())
start=time.time()
for i in range(6):
# p=Process(target=work)
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
5、程序池與執行緒池
思考:能否無限制開設程序或者執行緒?
不能,
如果從技術層面來講無限制開設是可以的,並且是最高效的
但是從硬體方面來講是無法實現的(硬體的發展永遠比不上軟體發展的速度)
池:
程序池:提前開設了固定個數的程序,之後反覆呼叫這些程序完成工作(不再開設新的程序)
執行緒池:提前開設固定個數的執行緒,之後反覆呼叫這些執行緒完成工作(之後不在開設新的執行緒)
# 程式碼實現程序池與執行緒池
# 高階程式碼
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool = ProcessPoolExecutir() # 開設程序池,括號內不新增數值那就與當前計算機的cpu數保持一致,內部有調傭當前CPU的數量的程式碼
pool = ThreadPoolExecutor() # 開設執行緒池,預設為CPU數量的倍
def task():
print('開始')
res = 1
for i in range(n):
res = res**i
time.sleep(1)
print(res)
# 向執行緒池中提交任務
pool.submit(task, 100)
print('主')
6、程序池與執行緒池基本使用
6、1執行緒池基本
"""1.0版本"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 建立執行緒池
pool = ThreadPoolExecutor(5)
# 定義一個任務
def tske(n):
print(n, os.getpid())
time.sleep(2)
return '>>>:%s'% n ** 2
# 等待所有執行緒執行完畢之後關閉執行緒池然後輸出結果
obj_list = []
for i in range(20):
res = pool.submit(tske, i) # 向執行緒池內新增要開設的執行緒與任務需要的引數
obj_list.append(res) # 將執行緒的返回結果新增到空列表,
# print(res.result()) # 直接列印結果。這屬於同步提交
pool.shutdown() # 等待所有執行緒執行結束後一次性列印執行緒提交的結果
for i in obj_list:
print(i.result()) # 等待結果全部都來在列印
"""1.1版本"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 建立執行緒池
pool = ThreadPoolExecutor(5)
# 定義一個任務
def tske(n):
print(n, os.getpid())
time.sleep(2)
return '>>>:%s'% n ** 2
# 定義一個回撥函式,非同步提交之後有結果立刻呼叫該函式
def call_back(a):
print('非同步回撥機制:%s'%a.result())
for i in range(20):
pool.submit(tske, i).add_done_callback(call_back) # 向執行緒池中提交任務,submit第一人蔘數是任務,第二個引數是任務的引數,之後點出方法,就是非同步回撥,新增的引數是函式
6、3程序池的基本使用
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 建立程序池
pool = ProcessPoolExecutor(5)
# 定義一個任務
def tske(n):
print(n, os.getpid())
time.sleep(2)
return n ** 2
# 定義一個回撥函式,非同步提交之後有結
def call_back(a):
print('非同步回撥機制:%s' % a.result())
start = time.time()
if __name__ == '__main__':
for i in range(10):
pool.submit(tske, i).add_done_callback(call_back)
pool.shutdown() # 等待所有程序執行完畢
# tske(20)
print(time.time()-start) # 計算時間
7、協程理論與實操
程序:
資源單位,類似工廠
執行緒:
每一個程序最少都要有一個執行緒,工作單位,類似工廠的流水線
協程:
程式設計師單方面意淫出來的名詞>>>:只要作用單執行緒下實現併發
什麼是併發:
切換+儲存狀態
以往學習的都是:多個任務(多個程序或者多個執行緒)來回切換
為何切換:
當程式碰到IO操作或者長時間佔用CPU資源
協程具體需要什麼才可以實現:(這裡只針對單執行緒下的IO操作)
需要程式沒有IO操作,或者讓CPU感覺不到程式有IO操作,自己檢測程式的IO操作並實現程式碼層面的切換
對於CPU而言這麼程式沒有IO,就會始終執行著這個程式,儘可能佔用CPU資源
"""
程式碼層面
需要第三方模組gevent:能夠自主檢測IO行為並切換
"""
from gevent import monkey;monkey.patch() # 固定程式碼格式
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5) # 程式中的IO操作
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3) # 程式中的IO操作
print('%s eat 2' % name)
start = time.time()
# play('jason') # 正常的同步呼叫,呼叫函式
# eat('jason') # 正常的同步呼叫,呼叫函式
g1 = spawn(play, 'jason') # 非同步提交
g2 = spawn(eat, 'jason') # 非同步提交
g1.join()
g2.join() # 等待被監測的任務執行完畢
print('主', time.time() - start) # 單執行緒下實現併發,提升效率
8、協程實現TCP服務端的併發效果
8.1、最基本的TCP服務端併發
import socket
from threading import Thread
from multiprocessing import Process
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data.decode('utf8'))
sock.send(data + b'gun dan!')
except ConnectionResetError as e:
print(e)
break
sock.close()
while True:
sock, addr = server.accept()
print(addr)
# 開設多程序或者多執行緒
t = Thread(target=talk, args=(sock,))
t.start()
8.2、新增協程
"""服務端"""
import socket
from gevent import monkey;
monkey.patch_all()
from gevent import spawn
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data)
sock.send(data + b'hello baby!')
except ConnectionResetError as e:
print(e)
sock.close()
break
def servers():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
while True:
sock, addr = server.accept()
spawn(talk, sock)
g1 = spawn(servers)
g1.join()
"""客戶端"""
from threading import Thread, current_thread
from socket import *
def client():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
9.1、IO模型
輪詢:
不停的詢問,直到有答案
9.1、阻塞IO
最為常見的io模型
有兩個等待階段
wiat for data, copy data
9.2、非阻塞IO
輪詢是否有資料(非常消耗資源)
系統呼叫變成了非阻塞狀態
只有一個等待階段
copy data
9.3、IO多路複用
利用select/epoll來監管多個程式,一旦某個程式需要的資料存在記憶體中裡,那麼立即通知該程式去取即可
9.4、非同步IO
發起一次系統呼叫,之後無需頻繁傳送,有結果並準備好之後會通知反饋機制反饋給呼叫方
非同步呼叫+反饋機制
自動提醒(回撥機制)