併發編程-線程-死鎖現象-GIL全局鎖-線程池

一堆鎖

死鎖現象 (重點)

死鎖指的是某個資源被佔用後,一直得不到釋放,致使其餘須要這個資源的線程進入阻塞狀態.python

  • 產生死鎖的狀況
    • 對同一把互斥鎖加了屢次
    • 一個共享資源,要訪問必須同時具有多把鎖,可是這些鎖被不一樣線程或者進程所持有,就會致使相互等待對方釋放從而程序就卡死了
  • 第二種狀況的解決方法:
    • 搶鎖必定按照相同的順序去搶
    • 給搶鎖加上超時,若是超時則放棄執行

遞歸鎖 (瞭解)

  • 與普通的區別程序員

    • 相同: 多線程之間都有互斥的效果
    • 不一樣: 同一個線程能夠對這個鎖執行屢次acquire
  • 解決方法安全

    同一個線程必須保證,加鎖的次數和解鎖的次數相同,其餘線程纔可以搶到這把鎖服務器

信號量 (瞭解)

能夠限制同時並執行公共代碼的線程數量網絡

若是限制數量爲1,則與普通互斥鎖沒有區別(默認爲1)多線程

from  threading import Semaphore,current_thread,Thread
import time

s = Semaphore(2)

def task():
    s.acquire()
    time.sleep(1)
    print(current_thread().name)
    s.release()


for i in range(10):
    Thread(target=task).start()

# 結果是每次都會執行兩個子線程

GIL全局鎖 (重點)

什麼是GIL鎖?併發

在cpython中,全局解釋器鎖(GIL,是爲了阻止多個本地線程在同一時間執行python字節碼的互斥鎖,app

由於cpython的內存管理是非線程安全的,這個鎖是很是必要的,由於其餘愈來愈多的特性依賴這個特性.異步

'''
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.)
'''

爲何須要GIL鎖

  • 線程安全問題具體的表現函數

    cpython解釋器與python程序之間的關係:

    • python程序本質就是一堆字符串,因此運行一個python程序時,必需要開啓一個解釋器,可是一個python程序中,解釋器只有一個,全部代碼都要交給它來解釋執行;所以當多個線程都要執行代碼時就會產生線程安全問題.

cpython解釋器與GC線程

GC線程: 執行python變量名管理的線程

python會自動幫咱們處理垃圾,清掃垃圾也是一堆代碼,所以也須要開啓一個線程來執行,這個線程就是GC線程.

而GC線程與咱們程序中的線程就會產生安全問題

例如: 線程a 要定義一個變量

  • 先申請一塊內存空間
  • 再把數據裝進去
  • 最後將引用計數加1

若是在進行到第二步的時候,CPU切換到了GC線程,GC線程就會把這個值當垃圾清理掉,這就會形成線程安全問題.

帶來的問題

GIL是一把互斥鎖,互斥鎖將致使效率下降

具體表現:

在cpython即使開啓了多線程,並且CPU也是多核的,卻沒法並行執行任務,由於解釋器只有一個,同一時間只有一個任務在執行.

如何解決 (重點)

沒辦法解決,只能儘量的避免GIL鎖影響咱們的效率

  1. 使用多進程可以實現並行,從而更好的利用多核CPU

  2. 對任務進行區分(分爲兩類): (重點)

    • 計算密集型:

      基本沒有IO 大部分時間在計算,例如:人臉識別/圖像處理

      因爲多線程不能 並行,因此應該使用多進程,將任務分給不一樣CPU核心

    • IO密集型:

      因爲網絡IO速度對比CPU處理速度很是慢多線程並不會形成太大的影響

      另外若有大量客戶端鏈接服務,進程根本開不起來,只能用多線程

關於新能的探討 (瞭解)

之因此加鎖是爲了解決線程安全問題,可是有了鎖,致使cpython中多線程不能並行,只能併發

可是並不能急就此否定python,有一下幾點緣由

  • python是一門語言,而GIL只是cpython解釋器的問題,只是python多解釋器中的一種
  • 若是是單核CPU,GIL不會形成任何影響
  • 因爲目前大多數程序都是基於網絡的,網絡速度對比CPU是很是慢的,即便CPU也沒法提升效率
  • 對於IO密集型任務,不會有太大的影響
  • 若是沒有這把鎖,咱們程序員就必須本身來解決安全問題

性能測試

from multiprocessing import Process
from threading import  Thread
import time
# # 計算密集型任務
#
# def task():
#     for i in range(100000000):
#         1+1
#
#
# if __name__ == '__main__':
#     start_time = time.time()
#
#     ps = []
#     for i in range(5):
#         p = Process(target=task)
#         # p = Thread(target=task)
#         p.start()
#         ps.append(p)
#
#     for i in ps:i.join()
#
#     print("共耗時:",time.time()-start_time)

# 多進程勝


# IO密集型任務

def task():
    for i in range(100):
        with open(r"1.死鎖現象.py",encoding="utf-8") as f:
            f.read()

if __name__ == '__main__':
    start_time = time.time()

    ps = []
    for i in range(10):
        p = Process(target=task)
        # p = Thread(target=task)
        p.start()
        ps.append(p)

    for i in ps:i.join()
    print("共耗時:",time.time()-start_time)

# 多線程勝

GIL鎖與自定義鎖的區別

  • GIL鎖,鎖住的是解釋器級別的數據
  • 自定義鎖,鎖的是解釋器之外的共享資源,例如:硬盤上的文件/ 控制檯,對於這種不屬於解釋器的數據資源就應該本身加鎖處理

線程池與進程池

  • 線程池: 就是裝線程的容器
  • 進程池: 就是裝進程的容器

爲何要裝到容器中

  1. 能夠避免頻繁的建立和銷燬(進行/線程)所形成的資源開銷
  2. 能夠限制同時存在的線程數量,以保證服務器不會應爲資源不足而致使崩潰
  3. 幫咱們管理了線程的生命週期
  4. 管理了任務的分配

若是進程不結束,池子裏面的進程或者線程也是一直存活的

import os
import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import activeCount,enumerate,currentThread

# # 建立一個線程池   指定最多能夠容納兩個線程
# pool = ThreadPoolExecutor(20)
#
# def task():
#     print(currentThread().name)
#
# # 提交任務到池子中
# pool.submit(task)
# pool.submit(task)
#
# print(enumerate())

# 進程池的使用

def task():
    time.sleep(1)
    print(os.getpid())


if __name__ == '__main__':
    pool = ProcessPoolExecutor(2)
    pool.submit(task)
    pool.submit(task)
    pool.submit(task)

同步與異步

  • 同步:

    指的是,提交任務後必須在原地等待,直到任務結束. 同步不等於阻塞

  • 異步:

    指的是,提交任務後不須要在原地等待,能夠繼續往下執行代碼

異步效率高於同步,異步任務將致使一個問題:就是任務的發起方不知道任務什麼時候處理完畢

異步/同步指的是提交任務的方式

解決方法:

  • 輪詢: 每隔一段時間就問一次

    效率低,沒法及時獲取結果 (不推薦使用)

  • 異步回調: 讓任務的執行方主動通知

    能夠及時拿到任務的結果,(推薦方式)

# 異步回調
from threading import Thread
# 具體的任務
def task(callback):
    print("run")
    for i in range(100000000):
        1+1
    callback("ok")
   

#回調函數 參數爲任務的結果
def finished(res):
    print("任務完成!",res)


print("start")
t = Thread(target=task,args=(finished,))
t.start()  #執行task時 沒有致使主線程卡主 而是繼續運行
print("over")

線程池中回調的使用

# 使用案例:
def task(num):
    time.sleep(1)
    print(num)
    return "hello python"

def callback(obj):
    print(obj.result())


pool = ThreadPoolExecutor()
res = pool.submit(task,123)
res.add_done_callback(callback)
print("over")
相關文章
相關標籤/搜索