三.併發編程 (線程鎖)

一 .線程鎖( Lock,RLock)

1. GIL(全局解釋器鎖)

GIL本質就是一把互斥鎖,既然是互斥鎖,全部互斥鎖的本質都同樣,都是將併發運行變成串行,以此來控制同一時間內共享數據只能被一個任務所修改,進而保證數據安全。
 能夠確定的一點是:保護不一樣的數據的安全,就應該加不一樣的鎖。

  Python中的線程是操做系統的原生線程,Python虛擬機使用一個全局解釋器鎖(Global Interpreter Lock)來互斥線程對Python虛擬機的使用。
爲了支持多線程機制,一個基本的要求就是須要實現不一樣線程對共享資源訪問的互斥,因此引入了GIL。 GIL:在一個線程擁有了解釋器的訪問權以後,其餘的全部線程都必須等待它釋放解釋器的訪問權,即便這些線程的下一條指令並不會互相影響。 在調用任何Python C API以前,要先得到GIL GIL缺點:多處理器退化爲單處理器;優勢:避免大量的加鎖解鎖操做
GIL的影響:
不管你啓多少個線程,你有多少個cpu, Python在執行一個進程的時候會淡定的在同一時刻只容許一個線程運行。
因此,python是沒法利用多核CPU實現多線程的。
這樣,python對於計算密集型的任務開多線程的效率甚至不如串行(沒有大量切換),可是,對於IO密集型的任務效率仍是有顯著提高的。

GIL保護的是解釋器級的數據,保護用戶本身的數據則須要本身加鎖處理 有了GIL的存在,同一時刻同一進程中只有一個線程被執行

全局解釋器鎖 --GIL  :同一個時刻有多個線程 可是隻能有一個線程方法CPU(同一時刻同一進程中只有一個線程被執行)

全局解釋器鎖 --GIL  鎖的是什麼:
                              鎖的是線程

是python語言的問題嗎:
                    不是 是Cpython解釋器的特性

多線程下每一個線程在執行的過程當中都須要先獲取GIL,保證同一時刻只有一個線程在運行

注意多線程內部有本身的數據棧 數不共享

描述Python GIL的概念, 以及它對python多線程的影響?編寫一個多線程抓取網頁的程序,並闡明多線程抓取程序是否可比單線程性能有提高,並解釋緣由。 1 .GIL:又叫全局解釋器鎖,每一個線程在執行的過程當中都須要先獲取GIL,保證同一時刻只有一個線程在運行,目的是解決多線程同時競爭程序中的全局變量而出現的線程安全問題
。它並非python語言的特性,僅僅是因爲歷史的緣由在CPython解釋器中難以移除,由於python語言運行環境大部分默認在CPython解釋器中。
2. https://blog.csdn.net/qq_40808154/article/details/89398076
3. https://blog.csdn.net/weixin_40612082/article/details/82559560
3. https://blog.csdn.net/weixin_43867210/article/details/85716990
 
 

 互斥鎖(同步鎖)

 鎖的目的是爲了保護共享的數據,同一時間只能有一個線程來修改共享的數據python

from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1)
    n=temp-1
if __name__ == '__main__':
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #結果可能爲99
print(n)   #結果可能爲99
# 多個線程搶佔資源的狀況      同步鎖
 
 

 互斥鎖(同步鎖)

 鎖的目的是爲了保護共享的數據,同一時間只能有一個線程來修改共享的數據安全


from
threading import Thread,Lock import time, os n = 100 def work(): global n lock.acquire() # 在改變數據以前加鎖 temp = n time.sleep(0.1) # 模擬IO,沒有io由於cpu執行速度太快,結果大部分正確,但爲了保證數據安全,最好加上鎖 n = temp - 1 lock.release() # 解鎖 # with lock: 自動加鎖解鎖 # temp=n # n=temp-1 if __name__ == '__main__': ti=[] lock = Lock() for i in range(100): # 開啓一百個線程 t = Thread(target=work) ti.append(t) t.start() for i in ti: i.join() print(n) print(n) # 結果確定爲0,由原來的併發執行變成串行,犧牲了執行效率保證了數據安全

鎖一般被用來實現對共享資源的同步訪問。爲每個共享資源建立一個Lock對象,當你須要訪問該資源時,調用acquire方法來獲取鎖對象(若是其它線程已經得到了該鎖,則當前線程需等待其被釋放)

待資源訪問完後,再調用release方法釋放鎖
import threading
R=threading.Lock()
R.acquire()
'''
對公共數據的操做
'''
R.release()

2. 互斥鎖和join比較

#不加鎖:併發執行,速度快,數據不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    global n
    print('%s is running' %current_thread().getName())
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''


#不加鎖:未加鎖部分併發執行,加鎖部分串行執行,速度慢,數據安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    #未加鎖的代碼併發運行
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    #加鎖的代碼串行運行
    lock.acquire()
    temp=n
    time.sleep(0.5)
    n=temp-1
    lock.release()

if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

#有的同窗可能有疑問:既然加鎖會讓運行變成串行,那麼我在start以後當即使用join,就不用加鎖了啊,也是串行的效果啊
#沒錯:在start以後馬上使用jion,確定會將100個任務的執行變成串行,毫無疑問,最終n的結果也確定是0,是安全的,但問題是
#start後當即join:任務內的全部代碼都是串行執行的,而加鎖,只是加鎖的部分即修改共享數據的部分是串行的
#單從保證數據安全方面,兩者均可以實現,但很明顯是加鎖的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗時是多麼的恐怖
'''

 3.死鎖和遞歸鎖(遞歸鎖來解決死鎖)

進程也有死鎖與遞歸鎖,在進程那裏忘記說了,放到這裏一切說了額

所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,
這些永遠在互相等待的進程稱爲死鎖進程,以下就是死鎖
死鎖
from
threading import Lock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()
 是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,



遞歸鎖
from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
遞歸鎖如同 一串鑰匙
能夠理解爲 一我的拿着拿着一串鑰匙開門(意思就是一層一層的往裏面開門)
出來也是帶着一串一串鑰匙出來

                                                                              

例如: 遞歸鎖

意思有多我的吃麪 可是桌子上只有一盤面     面上面有一把鎖   叉子上有一把鎖  
          (意思要拿到面的鎖和叉子的鎖  用鑰匙去開才能吃到面  吃到了面 把鑰匙還回去繼續下一我的   )

若是 有一我的拿到 面上的鎖   也是吃不不到面

若是 有一我的拿到 叉子鎖 也是吃不不到面
死鎖
若是 有一我的拿到 面上的鎖   也是吃不不到面
若是 有一我的拿到 叉子鎖 也是吃不不到面
import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 搶到了麪條'%name)
    fork_lock.acquire()
    print('%s 搶到了叉子'%name)
    print('%s 吃麪'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 搶到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 搶到了麪條' % name)
    print('%s 吃麪' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['張三','李四','王五']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
    

# 執行
張三 搶到了麪條
張三 搶到了叉子
張三 吃麪
張三 搶到了叉子
李四 搶到了麪條
遞歸鎖解決死鎖問題
# 例如:
# 意思有多我的吃麪 可是桌子上只有一盤面 面上面有一把鎖 叉子上有一把鎖
# (意思要拿到面的鎖和叉子的鎖 用鑰匙去開才能吃到面 吃到了面 把鑰匙還回去繼續下一我的 ) 依次有序 變成串行同步
# 若是 有一我的拿到 面上的鎖 也是吃不不到面
# 若是 有一我的拿到 叉子鎖 也是吃不不到面
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock() 鎖生產了一串鑰匙
def eat1(name):
    noodle_lock.acquire()  帶着一串鑰匙去執行下面
    print('%s 搶到了麪條'%name)
    fork_lock.acquire()
    print('%s 搶到了叉子'%name)
    print('%s 吃麪'%name)
    fork_lock.release()
    noodle_lock.release()還回一串鑰匙

def eat2(name):
    fork_lock.acquire()
    print('%s 搶到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 搶到了麪條' % name)
    print('%s 吃麪' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['張三','李四','王五']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()

執行
張三 搶到了麪條
張三 搶到了叉子
張三 吃麪
張三 搶到了叉子
張三 搶到了麪條
張三 吃麪
李四 搶到了麪條
李四 搶到了叉子
李四 吃麪
李四 搶到了叉子
李四 搶到了麪條
李四 吃麪
王五 搶到了麪條
王五 搶到了叉子
王五 吃麪
王五 搶到了叉子
王五 搶到了麪條
王五 吃麪
進程已結束,退出代碼 0
相關文章
相關標籤/搜索