1. 程式人生 > >孤荷淩寒自學python第三十九天python 的線程鎖Lock

孤荷淩寒自學python第三十九天python 的線程鎖Lock

對象 enume 理念 tel 2.3 模塊 線程安全 進行 ima

孤荷淩寒自學python第三十九天python的線程鎖Lock

(完整學習過程屏幕記錄視頻地址在文末,手寫筆記在文末)

當多個線程同時操作一個文件等需要同時操作某一對象的情況發生時,很有可能發生沖突,因此在這種情況下就只能允許實時只有一個線程在操作這一對象,而其它線程在隊列中等待,按排隊順序依次進行,這是我對【線程同步】的初步理解,因此【鎖】這個對象就是用於實時只允許一個線程操作多線程大家都要操作的對象而存在的,當一個線程使用開啟【鎖】對象後到解鎖【鎖】對象的這段時間,只就允許此線程(是指開啟【鎖】對象的線程)運行,而其它並行線程等待【鎖】的解除後才能運行。

為了控制復雜的多線程,python中有多種不同的鎖對象(包括高級鎖對象),今天我初步研究了Lock鎖對象,因為是自學,果然走了很多彎路,但卻覺得非常值得,因為親身真切地感受了Lock對象的特質,雖然沒有完全有把握說100%掌握了Lock對象的使用,但通過實踐得來的理解的印象是非常深刻的。

一、 得到Lock鎖對象

新的Lock鎖對象=threading.Lock()

使用Lock類的初始化方法之前,必須保證當前py文檔已聲明引用過threading模塊:

import threading

Lock是一個類嗎?只知道它其實來自於不被推薦使用的_thread模塊中的_thread.allocate_lock,就再也沒有線索了,由於很多資料及經驗文章中居然都沒有明確地指明此東西是什麽,我理解好一會兒才估計Lock是一個類。(因為其它鎖基本上都是類)這也告訴自己在理解時一定要徹底,表達時一定要用語精準,清晰,這是我發現很多博主寫博文時,以前自己以前似乎也是如此,總之只要自己能讀懂(實際可能是似懂非懂),就覺得以為自己講得很清楚了,事實呢,讀的人也許就難以理解了,於是我決定以後發表博文等一定要再三檢查,並讓至少三個人提出初讀意見後,再修改,以確定是表達明白了。

二、 Lock對象的主要方法

Lock對象有兩個主要的方法:

1

Lock鎖對象 . acquire()

當在指定線程的代碼塊中執行了鎖對象的acquire()方法,那麽,此線程將讓並行的其它線程處於暫停執行的等待狀態中,此線程將獨占運行。

2

Lock鎖對象 . release()

當正在獨占運行的線程執行了鎖對象的release()方法的時候,就是解除了鎖定操作,其它並行線程就可以繼續運行了。

三、 測試代碼一及事後反復研究推論結果

不用鎖的情況下,兩個並行線程操作同一個文件並輸出執行時間的測試,使用了來自time模塊中的sleep()方法

importthreading

fromdatetime import datetime

from time import sleep

strf=‘1.txt‘

def read():

for n in x:

with open(strf,‘r‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=f.readlines()

lstA.append(‘_讀取時間:‘ + strtime)

print(lstA)

sleep(1)

def write():

for n in x:

with open(strf,‘w‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=[‘負責寫的進程寫入時間:‘,strtime]

print(lstA)

f.writelines(lstA)

sleep(3)

def main():

tread=threading.Thread(target=read)

twrite=threading.Thread(target=write)

tread.start()

twrite.start()

tread.join()

twrite.join()

if __name__==‘__main__‘:

x=range(3)

main()

運行結果:

[‘寫入時間:2018-08-17 22:14:22.349675‘, ‘_讀取時間:2018-08-1822:17:03.589792‘]

[‘負責寫的進程寫入時間:‘, ‘2018-08-18 22:17:03.589792‘]

[‘負責寫的進程寫入時間:2018-08-18 22:17:03.589792‘, ‘_讀取時間:2018-08-1822:17:04.620917‘]

[‘負責寫的進程寫入時間:2018-08-18 22:17:03.589792‘, ‘_讀取時間:2018-08-1822:17:05.683155‘]

[‘負責寫的進程寫入時間:‘, ‘2018-08-18 22:17:06.620406‘]

[‘負責寫的進程寫入時間:‘, ‘2018-08-18 22:17:09.697863‘]

從兩個線程的執行來看,操作同一個文件沒有出現沖突,都寫入成功,也都讀取成功,且線程是並行運行的(輸出結果是交替出現的可以證明)但這並不是線程安全的同步操作方法。

四、 測試代碼二及事後反復研究推論結果

使用同一把鎖的情況下,兩個並行線程操作輸出執行時間的測試

importthreading

fromdatetime import datetime

from time import sleep

intcount=3

def f1(n):

lock.acquire() #從本行的下一行起,當前線程將獨占運行,其它並行線程將等待本線程的執行

if n==‘t0‘:

sleep(3)

elif n==‘t1‘:

sleep(2)

elif n==‘t2‘:

sleep(1)

else:

pass




strtime=str(datetime.now())

print(‘線程‘ + n + ‘正在運行中....線程啟動於:‘ +strtime)

lock.release() #到本行止,當前線程的獨占運行結束,其它並行線程將繼續並行運算

def main():

threads=[]

x=range(intcount)

for n in x:

t=threading.Thread(target=f1,args=(‘t‘+ str(n),))

#if n==1:

# t.setDaemon(True)

t.name=‘t‘ + str(n)

threads.append(t)

for n in x:

threads[n].start()

#print(threading.activeCount())

for item inthreading.enumerate():

print(item)

print(‘--------‘)

for item in threads:

print(item)

print(threads[0].isAlive())

for n in x:

threads[n].join()

if __name__==‘__main__‘:

lock=threading.Lock()

main()

運行結果:

<_MainThread(MainThread, started 1968)>

<Thread(t0, started 2032)>

<Thread(t1, started 6696)>

<Thread(t2, started 3372)>

--------

<Thread(t0, started 2032)>

<Thread(t1, started 6696)>

<Thread(t2, started 3372)>

True

線程t0正在運行中....線程啟動於:2018-08-1822:40:35.886018

線程t1正在運行中....線程啟動於:2018-08-18 22:40:37.894978

線程t2正在運行中....線程啟動於:2018-08-18 22:40:38.905267

從運行結果中,可以看出,鎖對象是成功執行的,嚴格按照線程隊列的預定隊列順序執行的,即:

如果沒有Lock鎖對象,則由於sleep的關系,t2線程反而要先打印自己的執行信息,而t0線程將最後打印自己的執行信息。

但由於使用了Lock鎖對象,按照三個線程聲明Lock.acquire的先後順序形成了隊列,則執行結果證明嚴格執行了t0-t1-t2的執行順序。

但程序執行造成了程序死鎖,我使用的VSCODE編輯工具都崩潰了。

後來才意識到,因為三個線程使用了同一個鎖,即是說:

當t0使用了lock.acquire()之後,還沒有等到t0使用lock.release(),緊急著啟動的線程t1又開始調用lock.acquire()命令——

這時由於同一個Lock鎖是不能連續兩次及以上使用:acquire()方法的,就必然造成了程序鎖死。(在我的測試過程中的屏幕錄像中可以看到,好像還沒有理解到被鎖死,懷疑是VSCODE編程器出問題了。)

五、 測試代碼三及事後反復研究推論結果

於是第三個測試是,使用三個鎖對象來對應三個線程,每個線程都使用一個獨立的鎖對象。

importthreading

fromdatetime import datetime

from time import sleep

intcount=3

def f1(n):

if n==‘t0‘:

lock1.acquire()

sleep(3)

elif n==‘t1‘:

lock2.acquire()

sleep(2)

elif n==‘t2‘:

lock3.acquire()

sleep(1)

else:

pass

strtime=str(datetime.now())

print(‘線程‘ + n + ‘正在運行中....線程啟動於:‘ +strtime)

if n==‘t0‘:

lock1.release()

elif n==‘t1‘:

lock2.release()

elif n==‘t2‘:

lock3.release()

else:

pass

def main():

threads=[]

x=range(intcount)

for n in x:

t=threading.Thread(target=f1,args=(‘t‘+ str(n),))

#if n==1:

# t.setDaemon(True)

t.name=‘t‘ + str(n)

threads.append(t)

for n in x:

threads[n].start()

#print(threading.activeCount())

for item inthreading.enumerate():

print(item)

print(‘--------‘)

for item inthreads:

print(item)

print(threads[0].isAlive())

for n in x:

threads[n].join()

if __name__==‘__main__‘:

lock1=threading.Lock()

lock2=threading.Lock()

lock3=threading.Lock()

main()

運行結果:

<_MainThread(MainThread, started 9864)>

<Thread(t0, started 6828)>

<Thread(t1, started 10052)>

<Thread(t2, started 1480)>

--------

<Thread(t0, started 6828)>

<Thread(t1, started 10052)>

<Thread(t2, started 1480)>

True

線程t2正在運行中....線程啟動於:2018-08-1822:54:49.059729

線程t1正在運行中....線程啟動於:2018-08-1822:54:50.070877

線程t0正在運行中....線程啟動於:2018-08-18 22:54:51.069221

從這一次的運行結果中可以看出,這一次的運行結果卻和沒有使用鎖對象沒有什麽區別,證明三個鎖對象獨立運用沒有發揮作用。

而且在測試當時認為程序沒有被 鎖死,但事後證明,盡管如此使用了三個鎖對象分別 獨立對應三個線程,但同時使用,事後多次測試證明整個程序運行還是被鎖死了,鑒於作為初學者我目前沒有理解本次執行也被鎖死的原因,需要高手解答為謝。

六、 測試代碼四及事後反復研究推論結果

這一次嘗試進行分別讀文件和寫文件兩個線程的防止沖突測試

importthreading

fromdatetime import datetime

from time import sleep

strf=‘1.txt‘

def read():

n=3

while n:

#sleep(2)

with open(strf,‘r‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=f.readlines()

print(‘讀取‘,strtime,lstA,‘\n\n‘)

n-=1

def write():

n=3

lock.acquire()

while n:

#sleep(1)

with open(strf,‘w‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=[‘寫入時間:‘,strtime]

print(lstA,‘\n\n‘)

f.writelines(lstA)

n-=1

lock.release

def main():

tread=threading.Thread(target=read)

twrite=threading.Thread(target=write)

twrite.start()

tread.start()

twrite.join()

tread.join()

print(‘主線程結束。‘)

if __name__==‘__main__‘:

lock=threading.Lock()

main()

以上代碼在VSCODE下的運行結果:

[‘寫入時間:‘, ‘2018-08-18 15:18:11.954452‘]

讀取2018-08-19 15:18:11.955450[]

[‘寫入時間:‘, ‘2018-08-18 15:18:11.956446‘]

讀取 2018-08-1815:18:11.957445 [‘寫入時間:‘, ‘2018-08-19 15:18:11.958444‘]

[]

讀取 2018-08-1815:18:11.959515 [‘寫入時間:2018-08-19 15:18:11.958444‘]

主線程結束。

此測試代碼歷經一個多小時的修改,目前看到的已經是修正過多次後的最終比較流暢的版本了,但從結果看出,其實兩個線程(讀)和(寫)實現了並行運行,預想中的效果時,當文件在處於寫的狀態時,是不允許讀的線程去執行的,但從結果看到,存在寫入失敗和讀取失敗的狀況,意味著線程的操作還是沖突了。

將以上代碼進行修改,加上了sleep語句。

importthreading

fromdatetime import datetime

from time import sleep

strf=‘1.txt‘

def read():

n=3

while n:

sleep(2)

with open(strf,‘r‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=f.readlines()

print(‘讀取‘,strtime,lstA,‘\n\n‘)

n-=1

def write():

n=3

lock.acquire()

while n:

sleep(1)

with open(strf,‘w‘,encoding=‘utf-8‘) as f:

strtime=str(datetime.now())

lstA=[‘寫入時間:‘,strtime]

print(lstA,‘\n\n‘)

f.writelines(lstA)

n-=1

lock.release

def main():

tread=threading.Thread(target=read)

twrite=threading.Thread(target=write)

twrite.start()

tread.start()

twrite.join()

tread.join()

print(‘主線程結束。‘)

if __name__==‘__main__‘:

lock=threading.Lock()

main()

運行結果:

[‘寫入時間:‘, ‘2018-08-18 15:28:27.335136‘]

讀取 2018-08-19 15:28:28.331552 [‘寫入時間:2018-08-1815:28:27.335136‘]

[‘寫入時間:‘, ‘2018-08-18 15:28:28.341305‘]

[‘寫入時間:‘, ‘2018-08-18 15:28:29.342920‘]

讀取 2018-08-18 15:28:30.350506 [‘寫入時間:2018-08-1815:28:29.342920‘]

讀取 2018-08-18 15:28:32.359019 [‘寫入時間:2018-08-1815:28:29.342920‘]

主線程結束。

在加上sleep語句後,程序 執行基本符合要求了,但仍然的,程序被鎖死,在顯示【主線程結束】的情況下,發現程序仍然處於鎖死狀態。目前無解,自己當局者迷中,亟待同學者特別是高手點撥。

下一研究的是RLock對象,不知能否解決Lock鎖不管怎樣都在VSCODE編輯器中鎖死的這些問題。

總體感覺,今天是完全淩亂了,亟需高手回復指導,萬分感激!!!

——————————

今天整理的學習筆記完成,最後例行說明下我的自學思路:

根據過去多年我自學各種編程語言的經歷,認為只有真正體驗式,解決實際問題式的學習才會有真正的效果,即讓學習實際發生。在2004年的時候我開始在一個鄉村小學自學電腦 並學習vb6編程語言,沒有學習同伴,也沒有高師在上,甚至電腦都是孤島(鄉村那時還沒有網絡),有的只是一本舊書,在痛苦的自學摸索中,我找到適應自己零基礎的學習方法:首先是每讀書的一小節就作相應的手寫筆記,第二步就是上機測試每一個筆記內容是否實現,其中會發現書中講的其實有出入或錯誤,第三步就是在上機測試之後,將筆記改為電子版,形成最終的修訂好的正確無誤的學習筆記。

通過反復嘗試錯誤,在那個沒有分享與交流的黑暗時期我摸黑學會了VB6,爾後接觸了其它語言,也曾聽過付費視頻課程,結果發現也許自己學歷果然太低,就算是零基礎的入門課程,其實也難以跟上進度,講師的教學多數出現對初學者的實際情況並不了解的情況,況且學習者的個體也存在差異呢?當然更可怕的是收費課程的價格往往是自己難以承受的。

於是我的所有編程學習都改為了自學,繼續自己的三步學習筆記法的學習之路。

當然自學的最大問題是會走那麽多的彎路,沒有導師直接輸入式的教學來得直接,好在網絡給我們帶來無限搜索的機會,大家在網絡上的學習日誌帶給我們共享交流的機會,而QQ群等交流平臺、網絡社區的成立,我們可以一起自學,互相批評交流,也可以獲得更有效,更自主的自學成果。

於是我以人生已過半的年齡,決定繼續我的編程自學之路,開始學習python,只希望與大家共同交流,一個人的獨行是可怕的,只有一群人的共同前進才是有希望的。

誠摯期待您的交流分享批評指點!歡迎聯系我加入從零開始的自學聯盟。

這個時代互聯網成為了一種基礎設施的存在,於是本來在孤獨學習之路上的我們變得不再孤獨,因為網絡就是一個新的客廳,我們時刻都可以進行沙龍活動。

非常樂意能與大家一起交流自己自學心得和發現,更希望大家能夠對我學習過程中的錯誤給予指點——是的,這樣我就能有許多免費的高師了——這也是分享時代,社區時代帶來的好福利,我相信大家會的,是吧!

根據完全共享的精神,開源互助的理念,我的個人自學錄制過程是全部按4K高清視頻錄制的,從手寫筆記到驗證手寫筆記的上機操作過程全程錄制,但因為4K高清文件太大均超過5G以上,所以無法上傳至網絡,如有需要可聯系我QQ578652607對傳,樂意分享。上傳分享到百度網盤的只是壓縮後的720P的視頻。

我的學習過程錄像百度盤地址分享如下:(清晰度:1280x720)

鏈接:https://pan.baidu.com/s/1C-uN7Igs7VDHQuFy-pZWRw

提取碼:sf37

Bilibili:

https://www.bilibili.com/video/av38089609/

喜馬拉雅語音筆記:

https://www.ximalaya.com/keji/19103006/145172481

技術分享圖片

孤荷淩寒自學python第三十九天python 的線程鎖Lock