[TOC] GIL全局解釋鎖 1. GIL本質上是一個互斥鎖。 2. GIL是為了阻止同一個進程內多個進程同時執行(並行) 單個進程下的多個線程無法實現並行,但能實現併發 3. 這把鎖主要是因為Cpython的記憶體管理不是線程安全的 保證線程在執行任務時不會被垃圾回收機制回收 多線程的作用 1. 計 ...
目錄
GIL全局解釋鎖
- GIL本質上是一個互斥鎖。
- GIL是為了阻止同一個進程內多個進程同時執行(並行)
- 單個進程下的多個線程無法實現並行,但能實現併發
這把鎖主要是因為Cpython的記憶體管理不是線程安全的
- 保證線程在執行任務時不會被垃圾回收機制回收
from threading import Thread
import time
num = 100
def task():
global num
num2 = num
time.sleep(1)
num = num2 - 1
print(num)
for line in range(100):
t = Thread(target=task)
t.start()
# 這裡的運行結果都是99, 加了IO操作,所有線程都對num進行了減值操作,由於GIL鎖的存在,沒有修改成功,都是99
多線程的作用
- 計算密集型, 有四個任務,每個任務需要10s
單核:
- 開啟進程
- 消耗資源過大
- 4個進程: 40s
- 開啟線程
- 消耗資源遠小於進程
- 4個線程: 40s
多核:
- 開啟進程
- 並行執行, 效率比較高
- 4個進程: 10s
- 開啟線程
- 併發執行,執行效率低
- 4個線程: 40s
- IO密集型, 四個任務, 每個任務需要10s
單核:
- 開啟進程
- 消耗資源過大
- 4個進程: 40s
- 開啟線程
- 消耗資源遠小於進程
- 4個線程: 40s
多核:
- 開啟進程
- 並行執行, 效率小於多線程, 但是遇到IO會立馬切換CPU的執行許可權
- 4個進程: 40s + 開啟進程消耗的額外時間
- 開啟線程
- 併發執行,執行效率高於多進程
- 4個線程: 40s
測試計算密集型
from threading import Thread
from multiprocessing import Process
import time
import os
# 計算密集型
def work1():
number = 0
for line in range(100000000):
number += 1
# IO密集型
def work2():
time.sleep(2)
if __name__ == '__main__':
# 測試計算密集型
print(os.cpu_count()) # 4核cpu
start = time.time()
list1 = []
for line in range(6):
p = Process(target=work1) # 程式執行時間8.756593704223633
# p = Thread(target=work1) # 程式執行時間31.78555393218994
list1.append(p)
p.start()
for p in list1:
p.join()
end = time.time()
print(f'程式執行時間{end - start}')
IO密集型
from threading import Thread
from multiprocessing import Process
import time
import os
# 計算密集型
def work1():
number = 0
for line in range(100000000):
number += 1
# IO密集型
def work2():
time.sleep(1)
if __name__ == '__main__':
# 測試計算密集型
print(os.cpu_count()) # 4核cpu
start = time.time()
list1 = []
for line in range(100):
# p = Process(target=work2) # 程式執行時間15.354223251342773
p = Thread(target=work2) # 程式執行時間1.0206732749938965
list1.append(p)
p.start()
for p in list1:
p.join()
end = time.time()
print(f'程式執行時間{end - start}')
結論:
- 在計算密集型的情況下, 使用多進程
- 在IO密集型的情況下, 使用多線程
- 高效執行多個進程, 內有多個IO密集型程式,使用多進程 + 多線程
死鎖現象
指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,如無外力作用,它們都無法推進下去.此時稱系統處於死鎖狀態
以下就是死鎖:
from threading import Thread, Lock
from threading import current_thread
import time
mutex_a = Lock()
mutex_b = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用戶{self.name}搶到鎖a')
mutex_b.acquire()
print(f'用戶{self.name}搶到鎖b')
mutex_b.release()
print(f'用戶{self.name}釋放鎖b')
mutex_a.release()
print(f'用戶{self.name}釋放鎖a')
def func2(self):
mutex_b.acquire()
print(f'用戶{self.name}搶到鎖b')
time.sleep(1)
mutex_a.acquire()
print(f'用戶{self.name}搶到鎖a')
mutex_a.release()
print(f'用戶{self.name}釋放鎖a')
mutex_b.release()
print(f'用戶{self.name}釋放鎖b')
for line in range(10):
t = MyThread()
t.start()
'''
用戶Thread-1搶到鎖a
用戶Thread-1搶到鎖b
用戶Thread-1釋放鎖b
用戶Thread-1釋放鎖a
用戶Thread-1搶到鎖b
用戶Thread-2搶到鎖a
'''
# 一直等待
遞歸鎖
用於解決死鎖問題
RLock: 比喻成萬能鑰匙,可以提供給多個人使用
但是第一個使用的時候,會對該鎖做一個引用計數
只有引用計數為0, 才能真正釋放讓一個人使用
上面的例子中用RLock代替Lock, 就不會發生死鎖現象
from threading import Thread, Lock, RLock
from threading import current_thread
import time
# mutex_a = Lock()
# mutex_b = Lock()
mutex_a = mutex_b = RLock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用戶{self.name}搶到鎖a')
mutex_b.acquire()
print(f'用戶{self.name}搶到鎖b')
mutex_b.release()
print(f'用戶{self.name}釋放鎖b')
mutex_a.release()
print(f'用戶{self.name}釋放鎖a')
def func2(self):
mutex_b.acquire()
print(f'用戶{self.name}搶到鎖b')
time.sleep(1)
mutex_a.acquire()
print(f'用戶{self.name}搶到鎖a')
mutex_a.release()
print(f'用戶{self.name}釋放鎖a')
mutex_b.release()
print(f'用戶{self.name}釋放鎖b')
for line in range(10):
t = MyThread()
t.start()
信號量(瞭解)
互斥鎖: 比喻成一個家用馬桶, 同一時間只能讓一個人去使用
信號比喻成公測多個馬桶: 同一時間可以讓多個人去使用
from threading import Semaphore
from threading import Thread
from threading import current_thread
import time
sm = Semaphore(5)
def task():
sm.acquire()
print(f'{current_thread().name}執行任務')
time.sleep(1)
sm.release()
for i in range(20):
t = Thread(target=task)
t.start()
線程隊列
線程Q: 就是線程隊列 FIFO
- 普通隊列: 先進先出 FIFO
- 特殊隊列: 後進先出 LIFO
- 優先順序隊列: 若傳入一個元組,會依次判斷參數的ASCII的數值大小
import queue
# 普通的線程隊列: 遵循先進先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 1
print(q.get()) # 2
# LIFO隊列 後進先出
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 3
# 優先順序隊列:根據參數內
q = queue.PriorityQueue()
q.put((4, '我'))
q.put((2, '你'))
q.put((3, 'ta'))
print(q.get()) # (2, '你')