Python學習【第23篇】:利用threading模組開執行緒 python併發程式設計之多執行緒1
一多執行緒的概念介紹
threading模組介紹
threading模組和multiprocessing模組在使用層面,有很大的相似性。
二、開啟多執行緒的兩種方式
1 1.建立執行緒的開銷比建立程序的開銷小,因而建立執行緒的速度快
2 from multiprocessing import Process
3 from threading import Thread
4 import os
5 import time
6 def work(): 7 print('<%s> is running'%os.getpid()) 8 time.sleep(2) 9 print('<%s> is done'%os.getpid()) 10 11 if __name__ == '__main__': 12 t=Thread(target=work,) 13 # t= Process(target=work,) 14 t.start() 15 print('主',os.getpid())
1 from threading import Thread
2 import time
3 class Work(Thread):
4 def __init__(self,name):
5 super().__init__()
6 self.name = name 7 def run(self): 8 # time.sleep(2) 9 print('%s say hell'%self.name) 10 if __name__ == '__main__': 11 t = Work('egon') 12 t.start() 13 print('主')
在一個程序下開啟多個執行緒與在一個程序下開啟多個子程序的區別
1 from multiprocessing import Process
2 from threading import Thread
3 import time
4 def work():
5 time.sleep(2)
6 print('hello') 7 if __name__ == '__main__': 8 t = Thread(target=work)#如果等上幾秒,他會在開啟的過程中先列印主,如果不等會先列印hello 9 # t = Process(target=work) #子程序會先列印主, 10 t.start() 11 print('主') 12
1 # 2.----------
2 from multiprocessing import Process
3 from threading import Thread
4 import os
5 def work():
6 print('hello',os.getpid())
7 if __name__ == '__main__': 8 #在主程序下開啟多個執行緒,每個執行緒都跟主程序的pid一樣 9 t1= Thread(target=work) 10 t2 = Thread(target=work) 11 t1.start() 12 t2.start() 13 print('主執行緒pid',os.getpid()) 14 15 #來多個程序,每個程序都有不同的pid 16 p1 = Process(target=work) 17 p2 = Process(target=work) 18 p1.start() 19 p2.start() 20 print('主程序pid', os.getpid())
1 from threading import Thread
2 from multiprocessing import Process
3 import os
4 def work():
5 global n
6 n-=1
7 print(n) #所以被改成99了
8 n = 100
9 if __name__ == '__main__': 10 # p = Process(target=work) 11 p = Thread(target=work) #當開啟的是執行緒的時候,因為同一程序內的執行緒之間共享程序內的資料 12 #所以列印的n為99 13 p.start() 14 p.join() 15 print('主',n) #毫無疑問子程序p已經將自己的全域性的n改成了0, 16 # 但改的僅僅是它自己的,檢視父程序的n仍然為100
程序之間是互相隔離的,不共享。需要藉助第三方來完成共享(藉助佇列,管道,共享資料)
三、練習
練習一:多執行緒實現併發
1 from socket import *
2 from threading import Thread
3 s = socket(AF_INET,SOCK_STREAM)
4 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #埠重用
5 s.bind(('127.0.0.1',8081))
6 s.listen(5)
7 print('start running...')
8 def talk(coon,addr): 9 while True: # 通訊迴圈 10 try: 11 cmd = coon.recv(1024) 12 print(cmd.decode('utf-8')) 13 if not cmd: break 14 coon.send(cmd.upper()) 15 print('傳送的是%s'%cmd.upper().decode('utf-8')) 16 except Exception: 17 break 18 coon.close() 19 if __name__ == '__main__': 20 while True:#連結迴圈 21 coon,addr = s.accept() 22 print(coon,addr) 23 p =Thread(target=talk,args=(coon,addr)) 24 p.start() 25 s.close()
1 from socket import *
2 c = socket(AF_INET,SOCK_STREAM)
3 c.connect(('127.0.0.1',8081))
4 while True:
5 cmd = input('>>:').strip()
6 if not cmd:continue
7 c.send(cmd.encode('utf-8'))
8 data = c.recv(1024) 9 print('接受的是%s'%data.decode('utf-8')) 10 c.close()
練習二:三個任務,一個接收使用者輸入,一個將使用者輸入的內容格式化成大寫,一個將格式化後的結果存入檔案
1 from threading import Thread
2 import os
3 input_l = []
4 format_l = []
5 def talk(): #監聽輸入任務
6 while True:
7 cmd = input('>>:').strip() 8 if not cmd:continue 9 input_l.append(cmd) 10 11 def format(): 12 while True: 13 if input_l: 14 res = input_l.pop()#取出來 15 format_l.append(res.upper()) #取出來後變大寫 16 def save(): 17 while True: 18 if format_l: #如果format_l不為空 19 with open('db','a') as f: 20 f.write(format_l.pop()+'\n') #寫進檔案 21 f.flush() 22 if __name__ == '__main__': 23 t1=Thread(target=talk) 24 t2=Thread(target=format) 25 t3=Thread(target=save) 26 t1.start() 27 t2.start() 28 t3.start()
四、多執行緒共享同一個程序內的地址空間
1 from threading import Thread
2 from multiprocessing import Process
3 import os
4 n = 100
5 def talk():
6 global n
7 n-=100
8 print(n) 9 if __name__ == '__main__': 10 t = Thread(target=talk) #如果開啟的是執行緒的話,n=0 11 # t = Process(target=talk) #如果開啟的是程序的話,n=100 12 t.start() 13 t.join() 14 print('主',n)
五、執行緒物件的其他屬性和方法
Thread例項物件的方法
# isAlive(): 返回執行緒是否活動的。
# getName(): 返回執行緒名。
# setName(): 設定執行緒名。
threading模組提供的一些方法:
# threading.currentThread(): 返回當前的執行緒變數。
# threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒。
# threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果。
1 from threading import Thread
2 from multiprocessing import Process
3 import time,os,threading
4 def work():
5 time.sleep(2)
6 print('%s is running' % threading.currentThread().getName()) 7 print(threading.current_thread()) #其他執行緒 8 print(threading.currentThread().getName()) #得到其他執行緒的名字 9 if __name__ == '__main__': 10 t = Thread(target=work) 11 t.start() 12 13 print(threading.current_thread().getName()) #主執行緒的名字 14 print(threading.current_thread()) #主執行緒 15 print(threading.enumerate()) #連同主執行緒在內有兩個執行的執行緒 16 time.sleep(2) 17 print(t.is_alive()) #判斷執行緒是否存活 18 print(threading.activeCount()) 19 print('主')
六、join與守護執行緒
主程序等所有的非守護的子程序結束他才結束(回收它子程序的資源):(有父子關係)
主執行緒等非守護執行緒全都結束它才結束: (沒父子關係)
1 from threading import Thread
2 import time,os
3 def talk():
4 time.sleep(3)
5 print('%s is running..'%os.getpid())
6 if __name__ == '__main__': 7 t = Thread(target=talk) 8 t.start() 9 t.join() #主程序在等子程序結束 10 print('主')
守護執行緒與守護程序的區別
1.守護程序:主程序會等到所有的非守護程序結束,才銷燬守護程序。也就是說(主程序執行完了被守護的那個就幹掉了)
2.守護執行緒:主執行緒執行完了守護的那個還沒有幹掉,主執行緒等非守護執行緒全都結束它才結束
1 from multiprocessing import Process
2 from threading import Thread,currentThread
3 import time,os
4 def talk1():
5 time.sleep(2)
6 print('hello') 7 def talk2(): 8 time.sleep(2) 9 print('you see see') 10 if __name__ == '__main__': 11 t1 = Thread(target=talk1) 12 t2 = Thread(target=talk2) 13 # t1 = Process(target=talk1) 14 # t2 = Process(target=talk2) 15 t1.daemon = True 16 t1.start() 17 t2.start() 18 print('主執行緒',os.getpid())
1 #3 --------迷惑人的例子
2 from threading import Thread
3 import time
4 def foo():
5 print(123)
6 # time.sleep(10) #如果這個等的時間大於下面等的時間,就把不列印end123了
7 time.sleep(2) #如果這個等的時間小於下面等的時間,就把end123也列印了
8 print('end123')
9 def bar(): 10 print(456) 11 # time.sleep(5) 12 time.sleep(10) 13 print('end456') 14 if __name__ == '__main__': 15 t1 = Thread(target=foo) 16 t2 = Thread(target=bar) 17 t1.daemon = True #主執行緒執行完了守護的那個還沒有幹掉, 18 # 主執行緒等非守護執行緒全都結束它才結束 19 t1.start() 20 t2.start() 21 print('main---------')
七、GIL與Lock
1.python GIL(Global Interpreter Lock) #全域性的直譯器鎖
2.鎖的目的:犧牲了效率,保證了資料的安全
3.保護不同的資料加不同的鎖()
4.python自帶垃圾回收
5.誰拿到GIL鎖就讓誰得到Cpython直譯器的執行許可權
6.GIT鎖保護的是Cpython直譯器資料的安全,而不會保護你自己程式的資料的安全
7.GIL鎖當遇到阻塞的時候,就被迫的吧鎖給釋放了,那麼其他的就開始搶鎖了,搶到
後吧值修改了,但是第一個拿到的還在原本拿到的那個資料的那停留著呢,當再次拿
到鎖的時候,資料已經修改了,而你還拿的原來的,這樣就混亂了,所以也就保證不了
資料的安全了。
8.那麼怎麼解決資料的安全ne ?
自己再給加吧鎖:mutex=Lock()
同步鎖
GIL 與Lock是兩把鎖,保護的資料不一樣,前者是直譯器級別的(當然保護的就是直譯器級別的資料,比如垃圾回收的資料),後者是保護使用者自己開發的應用程式的資料,很明顯GIL不負責這件事,只能使用者自定義加鎖處理,即Lock
過程分析:所有執行緒搶的是GIL鎖,或者說所有執行緒搶的是執行許可權
執行緒1搶到GIL鎖,拿到執行許可權,開始執行,然後加了一把Lock,還沒有執行完畢,即執行緒1還未釋放Lock,有可能執行緒2搶到GIL鎖,開始執行,執行過程中發現Lock還沒有被執行緒1釋放,於是執行緒2進入阻塞,被奪走執行許可權,有可能執行緒1拿到GIL,然後正常執行到釋放Lock。。。這就導致了序列執行的效果
既然是序列,那我們執行
t1.start()
t1.join
t2.start()
t2.join()
這也是序列執行啊,為何還要加Lock呢,需知join是等待t1所有的程式碼執行完,相當於鎖住了t1的所有程式碼,而Lock只是鎖住一部分操作共享資料的程式碼。
因為Python直譯器幫你自動定期進行記憶體回收,你可以理解為python直譯器裡有一個獨立的執行緒,每過一段時間它起wake up做一次全域性輪詢看看哪些記憶體資料是可以被清空的,此時你自己的程式 裡的執行緒和 py直譯器自己的執行緒是併發執行的,假設你的執行緒刪除了一個變數,py直譯器的垃圾回收執行緒在清空這個變數的過程中的clearing時刻,可能一個其它執行緒正好又重新給這個還沒來及得清空的記憶體空間賦值了,結果就有可能新賦值的資料被刪除了,為了解決類似的問題,python直譯器簡單粗暴的加了鎖,即當一個執行緒執行時,其它人都不能動,這樣就解決了上述的問題, 這可以說是Python早期版本的遺留問題。
1 from threading import Thread,Lock
2 import time
3 n=100
4 def work():
5 mutex.acquire()
6 global n
7 temp=n 8 time.sleep(0.01) 9 n=temp-1 10 mutex.release() 11 if __name__ == '__main__': 12 mutex=Lock() 13 t_l=[] 14 s=time.time() 15 for i in range(100): 16 t=Thread(target=work) 17 t_l.append(t) 18 t.start() 19 for t in t_l: 20 t.join() 21 print('%s:%s' %(time.time()-s,n))
鎖通常被用來實現對共享資源的同步訪問。為每一個共享資源建立一個Lock物件,當你需要訪問該資源時,呼叫acquire方法來獲取鎖物件(如果其它執行緒已經獲得了該鎖,則當前執行緒需等待其被釋放),待資源訪問完後,再呼叫release方法釋放鎖:
1 import threading
2 mutex = threading.Lock()
3 mutex.aquire()
4 '''
5 對公共資料的操作
6 '''
7 mutex.release()
1 分析:
2 1.100個執行緒去搶GIL鎖,即搶執行許可權
3 2. 肯定有一個執行緒先搶到GIL(暫且稱為執行緒1),然後開始執行,一旦執行就會拿到lock.acquire()
4 3. 極有可能執行緒1還未執行完畢,就有另外一個執行緒2搶到GIL,然後開始執行,但執行緒2發現互斥鎖lock還未被執行緒1釋放,於是阻塞,被迫交出執行許可權,即釋放GIL
5 4.直到執行緒1重新搶到GIL,開始從上次暫停的位置繼續執行,直到正常釋放互斥鎖lock,然後其他的執行緒再重複2 3 4的過程
如果不加鎖:併發執行,速度快,資料不安全。
加鎖:序列執行,速度慢,資料安全。
1 #不加鎖:併發執行,速度快,資料不安全
2 from threading import current_thread,Thread,Lock
3 import os,time
4 def task():
5 global n
6 print('%s is running' %current_thread().getName())
7 temp=n 8 time.sleep(0.5) 9 n=temp-1 10 11 12 if __name__ == '__main__': 13 n=100 14 lock=Lock() 15 threads=[] 16 start_time=time.time() 17 for i in range(100): 18 t=Thread(target=task) 19 threads.append(t) 20 t.start() 21 for t in threads: 22 t.join() 23 24 stop_time=time.time() 25 print('主:%s n:%s' %(stop_time-start_time,n)) 26 27 ''' 28 Thread-1 is running 29 Thread-2 is running 30 ...... 31 Thread-100 is running 32 主:0.5216062068939209 n:99 33 ''' 34 35 36 #不加鎖:未加鎖部分併發執行,加鎖部分序列執行,速度慢,資料安全 37 from threading import current_thread,Thread,Lock 38 import os,time 39 def task(): 40 #未加鎖的程式碼併發執行 41 time.sleep(3) 42 print('%s start to run' %current_thread().getName()) 43 global n 44 #加鎖的程式碼序列執行 45 lock.acquire() 46 temp=n 47 time.sleep(0.5) 48 n=temp-1 49 lock.release() 50 51 if __name__ == '__main__': 52 n=100 53 lock=Lock() 54 threads=[] 55 start_time=time.time() 56 for i in range(100): 57 t=Thread(target=task) 58 threads.append(t) 59 t.start() 60 for t in threads: 61 t.join() 62 stop_time=time.time() 63 print('主:%s n:%s' %(stop_time-start_time,n)) 64 65 ''' 66 Thread-1 is running 67 Thread-2 is running 68 ...... 69 Thread-100 is running 70 主:53.294203758239746 n:0 71 ''' 72 73 #有的同學可能有疑問:既然加鎖會讓執行變成序列,那麼我在start之後立即使用join,就不用加鎖了啊,也是序列的效果啊 74 #沒錯:在start之後立刻使用jion,肯定會將100個任務的執行變成序列,毫無疑問,最終n的結果也肯定是0,是安全的,但問題是 75 #start後立即join:任務內的所有程式碼都是序列執行的,而加鎖,只是加鎖的部分即修改共享資料的部分是序列的 76 #單從保證資料安全方面,二者都可以實現,但很明顯是加鎖的效率更高. 77 from threading import current_thread,Thread,Lock 78 import os,time 79 def task(): 80 time.sleep(3) 81 print('%s start to run' %current_thread().getName()) 82 global n 83 temp=n 84 time.sleep(0.5) 85 n=temp-1 86 87 88 if __name__ == '__main__': 89 n=100 90 lock=Lock() 91 start_time=time.time() 92 for i in range(100): 93 t=Thread(target=task) 94 t.start() 95 t.join() 96 stop_time=time.time() 97 print('主:%s n:%s' %(stop_time-start_time,n)) 98 99 ''' 100 Thread-1 start to run 101 Thread-2 start to run 102 ...... 103 Thread-100 start to run 104 主:350.6937336921692 n:0 #耗時是多麼的恐怖 105 '''
一多執行緒的概念介紹
threading模組介紹
threading模組和multiprocessing模組在使用層面,有很大的相似性。
二、開啟多執行緒的兩種方式
1 1.建立執行緒的開銷比建立程序的開銷小,因而建立執行緒的速度快
2 from multiprocessing import Process
3 from threading import Thread
4 import os
5 import time
6 def work(): 7 print('<%s> is running'%os.getpid()) 8 time.sleep(2) 9 print('<%s> is done'%os.getpid()) 10 11 if __name__ == '__main__': 12 t=Thread(target=work,) 13 # t= Process(target=work,) 14 t.start() 15 print('主',os.getpid())
1 from threading import Thread
2 import time
3 class Work(Thread):
4 def __init__(self,name):
5 super().__init__()
6 self.name = name 7 def run(self): 8 # time.sleep(2) 9 print('%s say hell'%self.name) 10 if __name__ == '__main__': 11 t = Work('egon') 12 t.start() 13 print('主')
在一個程序下開啟多個執行緒與在一個程序下開啟多個子程序的區別
1 from multiprocessing import Process
2 from threading import Thread
3 import time
4 def work():
5 time.sleep(2)
6 print('hello') 7 if __name__ == '__main__': 8 t = Thread(target=work)#如果等上幾秒,他會在開啟的過程中先列印主,如果不等會先列印hello 9 # t = Process(target=work) #子程序會先列印主, 10 t.start() 11 print('主') 12
1 # 2.----------
2 from multiprocessing import Process
3 from threading import Thread
4 import os
5 def work():
6 print('hello',os.getpid())
7 if __name__ == '__main__': 8 #在主程序下開啟多個執行緒,每個執行緒都跟主程序的pid一樣 9 t1= Thread(target=work) 10 t2 = Thread(target=work) 11 t1.start() 12 t2.start() 13 print('主執行緒pid',os.getpid()) 14 15 #來多個程序,每個程序都有不同的pid 16 p1 = Process(target=work) 17 p2 = Process(target=work) 18 p1.start() 19 p2.start() 20 print('主程序pid', os.getpid())
1 from threading import Thread
2 from multiprocessing import Process
3 import os
4 def work():
5 global n
6 n-=1
7 print(n) #所以被改成99了
8 n = 100
9 if __name__ == '__main__': 10 # p = Process(target=work) 11 p = Thread(target=work) #當開啟的是執行緒的時候,因為同一程序內的執行緒之間共享程序內的資料 12 #所以列印的n為99 13 p.start() 14 p.join() 15 print('主',n) #毫無疑問子程序p已經將自己的全域性的n改成了0, 16 # 但改的僅僅是它自己的,檢視父程序的n仍然為100
程序之間是互相隔離的,不共享。需要藉助第三方來完成共享(藉助佇列,管道,共享資料)
三、練習
練習一:多執行緒實現併發
1 from socket import *
2 from threading import Thread
3 s = socket(AF_INET,SOCK_STREAM)
4 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #埠重用
5 s.bind(('127.0.0.1',8081))
6 s.listen(5)
7 print('start running...')
8 def talk(coon,addr): 9 while True: # 通訊迴圈 10 try: 11 cmd = coon.recv(1024) 12 print(cmd.decode('utf-8')) 13 if not cmd: break 14 coon.send(cmd.upper()) 15 print('傳送的是%s'%cmd.upper().decode('utf-8')) 16 except Exception: 17 break 18 coon.close() 19 if __name__ == '__main__': 20 while True:#連結迴圈 21 coon,addr = s.accept() 22 print(coon,addr) 23 p =Thread(target=talk,args=(coon,addr)) 24 p.start() 25 s.close()
1 from socket import *
2 c = socket(AF_INET,SOCK_STREAM)
3 c.connect(('127.0.0.1',8081))
4 while True:
5 cmd = input('>>:').strip()
6 if not cmd:continue
7 c.send(cmd.encode('utf-8'))
8 data = c.recv(1024) 9 print('接受的是%s'%data.decode('utf-8')) 10 c.close()
練習二:三個任務,一個接收使用者輸入,一個將使用者輸入的內容格式化成大寫,一個將格式化後的結果存入檔案
1 from threading import Thread
2 import os
3 input_l = []
4 format_l = []
5 def talk(): #監聽輸入任務
6 while True:
7 cmd = input('>>:').strip() 8 if not cmd:continue 9 input_l.append(cmd) 10 11 def format(): 12 while True: 13 if input_l: 14 res = input_l.pop()#取出來 15 format_l.append(res.upper()) #取出來後變大寫 16 def save(): 17 while True: 18 if format_l: #如果format_l不為空 19 with open('db','a') as f: 20 f.write(format_l.pop()+'\n') #寫進檔案 21 f.flush() 22 if __name__ == '__main__': 23 t1=Thread(target=talk) 24 t2=Thread(target=format) 25 t3=Thread(target=save) 26 t1.start() 27 t2.start() 28 t3.start()
四、多執行緒共享同一個程序內的地址空間
1 from threading import Thread
2 from multiprocessing import Process
3 import os
4 n = 100
5 def talk():
6 global n
7 n-=100
8 print(n) 9 if __name__ == '__main__': 10 t = Thread(target=talk) #如果開啟的是執行緒的話,n=0 11 # t = Process(target=talk) #如果開啟的是程序的話,n=100 12 t.start() 13 t.join() 14 print('主',n)
五、執行緒物件的其他屬性和方法
Thread例項物件的方法
# isAlive(): 返回執行緒是否活動的。
# getName(): 返回執行緒名。
# setName(): 設定執行緒名。
threading模組提供的一些方法:
# threading.currentThread(): 返回當前的執行緒變數。
# threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒。
# threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果。
1 from threading import Thread
2 from multiprocessing import Process
3 import time,os,threading
4 def work():
5 time.sleep(2)
6 print('%s is running' % threading.currentThread().getName()) 7 print(threading.current_thread()) #其他執行緒 8 print(threading.currentThread().getName()) #得到其他執行緒的名字 9 if __name__ == '__main__': 10 t = Thread(target=work) 11 t.start() 12 13 print(threading.current_thread().getName()) #主執行緒的名字 14 print(threading.current_thread()) #主執行緒 15 print(threading.enumerate()) #連同主執行緒在內有兩個執行的執行緒 16 time.sleep(2) 17 print(t.is_alive()) #判斷執行緒是否存活 18 print(threading.activeCount()) 19 print('主')
六、join與守護執行緒
主程序等所有的非守護的子程序結束他才結束(回收它子程序的資源):(有父子關係)
主執行緒等非守護執行緒全都結束它才結束: (沒父子關係)
1 from threading import Thread
2 import time,os
3 def talk():
4 time.sleep(3)
5 print('%s is running..'%os.getpid())
6 if __name__ == '__main__': 7 t = Thread(target=talk) 8 t.start() 9 t.join() #主程序在等子程序結束 10 print('主')
守護執行緒與守護程序的區別
1.守護程序:主程序會等到所有的非守護程序結束,才銷燬守護程序。也就是說(主程序執行完了被守護的那個就幹掉了)
2.守護執行緒:主執行緒執行完了守護的那個還沒有幹掉,主執行緒等非守護執行緒全都結束它才結束
1 from multiprocessing import Process
2 from threading import Thread,currentThread
3 import time,os
4 def talk1():
5 time.sleep(2)
6 print('hello') 7 def talk2(): 8 time.sleep(2) 9 print('you see see') 10 if __name__ == '__main__': 11 t1 = Thread(target=talk1) 12 t2 = Thread(target=talk2) 13 # t1 = Process(target=talk1) 14 # t2 = Process(target=talk2) 15 t1.daemon = True 16 t1.start() 17 t2.start() 18 print('主執行緒',os.getpid())
1 #3 --------迷惑人的例子
2 from threading import Thread
3 import time
4 def foo():
5 print(123)
6 # time.sleep(10) #如果這個等的時間大於下面等的時間,就把不列印end123了
7 time.sleep(2) #如果這個等的時間小於下面等的時間,就把end123也列印了
8 print('end123')
9 def bar(): 10 print(456) 11 # time.sleep(5) 12 time.sleep(10) 13 print('end456') 14 if __name__ == '__main__': 15 t1 = Thread(target=foo) 16 t2 = Thread(target=bar) 17 t1.daemon = True #主執行緒執行完了守護的那個還沒有幹掉, 18 # 主執行緒等非守護執行緒全都結束它才結束 19 t1.start() 20 t2.start() 21 print('main---------')
七、GIL與Lock
1.python GIL(Global Interpreter Lock) #全域性的直譯器鎖
2.鎖的目的:犧牲了效率,保證了資料的安全
3.保護不同的資料加不同的鎖()
4.python自帶垃圾回收
5.誰拿到GIL鎖就讓誰得到Cpython直譯器的執行許可權
6.GIT鎖保護的是Cpython直譯器資料的安全,而不會保護你自己程式的資料的安全
7.GIL鎖當遇到阻塞的時候,就被迫的吧鎖給釋放了,那麼其他的就開始搶鎖了,搶到
後吧值修改了,但是第一個拿到的還在原本拿到的那個資料的那停留著呢,當再次拿
到鎖的時候,資料已經修改了,而你還拿的原來的,這樣就混亂了,所以也就保證不了
資料的安全了。
8.那麼怎麼解決資料的安全ne ?
自己再給加吧鎖:mutex=Lock()
同步鎖
GIL 與Lock是兩把鎖,保護的資料不一樣,前者是直譯器級別的(當然保護的就是直譯器級別的資料,比如垃圾回收的資料),後者是保護使用者自己開發的應用程式的資料,很明顯GIL不負責這件事,只能使用者自定義加鎖處理,即Lock
過程分析:所有執行緒搶的是GIL鎖,或者說所有執行緒搶的是執行許可權
執行緒1搶到GIL鎖,拿到執行許可權,開始執行,然後加了一把Lock,還沒有執行完畢,即執行緒1還未釋放Lock,有可能執行緒2搶到GIL鎖,開始執行,執行過程中發現Lock還沒有被執行緒1釋放,於是執行緒2進入阻塞,被奪走執行許可權,有可能執行緒1拿到GIL,然後正常執行到釋放Lock。。。這就導致了序列執行的效果
既然是序列,那我們執行
t1.start()
t1.join
t2.start()
t2.join()
這也是序列執行啊,為何還要加Lock呢,需知join是等待t1所有的程式碼執行完,相當於鎖住了t1的所有程式碼,而Lock只是鎖住一部分操作共享資料的程式碼。
因為Python直譯器幫你自動定期進行記憶體回收,你可以理解為python直譯器裡有一個獨立的執行緒,每過一段時間它起wake up做一次全域性輪詢看看哪些記憶體資料是可以被清空的,此時你自己的程式 裡的執行緒和 py直譯器自己的執行緒是併發執行的,假設你的執行緒刪除了一個變數,py直譯器的垃圾回收執行緒在清空這個變數的過程中的clearing時刻,可能一個其它執行緒正好又重新給這個還沒來及得清空的記憶體空間賦值了,結果就有可能新賦值的資料被刪除了,為了解決類似的問題,python直譯器簡單粗暴的加了鎖,即當一個執行緒執行時,其它人都不能動,這樣就解決了上述的問題, 這可以說是Python早期版本的遺留問題。
1 from threading import Thread,Lock
2 import time
3 n=100
4 def work():
5 mutex.acquire()
6 global n
7 temp=n 8 time.sleep(0.01) 9 n=temp-1 10 mutex.release() 11 if __name__ == '__main__': 12 mutex=Lock() 13 t_l=[] 14 s=time.time() 15 for i in range(100): 16 t=Thread(target=work) 17 t_l.append(t) 18 t.start() 19 for t in t_l: 20 t.join() 21 print('%s:%s' %(time.time()-s,n))
鎖通常被用來實現對共享資源的同步訪問。為每一個共享資源建立一個Lock物件,當你需要訪問該資源時,呼叫acquire方法來獲取鎖物件(如果其它執行緒已經獲得了該鎖,則當前執行緒需等待其被釋放),待資源訪問完後,再呼叫release方法釋放鎖:
1 import threading
2 mutex = threading.Lock()
3 mutex.aquire()
4 '''
5 對公共資料的操作
6 '''
7 mutex.release()
1 分析:
2 1.100個執行緒去搶GIL鎖,即搶執行許可權
3 2. 肯定有一個執行緒先搶到GIL(暫且稱為執行緒1),然後開始執行,一旦執行就會拿到lock.acquire()
4 3. 極有可能執行緒1還未執行完畢,就有另外一個執行緒2搶到GIL,然後開始執行,但執行緒2發現互斥鎖lock還未被執行緒1釋放,於是阻塞,被迫交出執行許可權,即釋放GIL
5 4.直到執行緒1重新搶到GIL,開始從上次暫停的位置繼續執行,直到正常釋放互斥鎖lock,然後其他的執行緒再重複2 3 4的過程
如果不加鎖:併發執行,速度快,資料不安全。
加鎖:序列執行,速度慢,資料安全。
1 #不加鎖:併發執行,速度快,資料不安全
2 from threading import current_thread,Thread,Lock
3 import os,time
4 def task():
5 global n
6 print('%s is running' %current_thread().getName())
7 temp=n 8 time.sleep(0.5) 9 n=temp-1 10 11 12 if __name__ == '__main__': 13 n=100 14 lock=Lock() 15 threads=[] 16 start_time=time.time() 17 for i in range(100): 18 t=Thread(target=task) 19 threads.append(t) 20 t.start() 21 for t in threads: 22 t.join() 23 24 stop_time=time.time() 25 print('主:%s n:%s' %(stop_time-start_time,n)) 26 27 ''' 28 Thread-1 is running 29 Thread-2 is running 30 ...... 31 Thread-100 is running 32 主:0.5216062068939209 n:99 33 ''' 34 35 36 #不加鎖:未加鎖部分併發執行,加鎖部分序列執行,速度慢,資料安全 37 from threading import current_thread,Thread,Lock 38 import os,time 39 def task(): 40 #未加鎖的程式碼併發執行 41 time.sleep(3) 42 print('%s start to run' %current_thread().getName()) 43 global n 44 #加鎖的程式碼序列執行 45 lock.acquire() 46 temp=n 47 time.sleep(0.5) 48 n=temp-1 49 lock.release() 50 51 if __name__ == '__main__': 52 n=100 53 lock=Lock() 54 threads=[] 55 start_time=time.time() 56 for i in range(100): 57 t=Thread(target=task) 58 threads.append(t) 59 t.start() 60 for t in threads: 61 t.join() 62 stop_time=time.time() 63 print('主:%s n:%s' %(stop_time-start_time,n)) 64 65 ''' 66 Thread-1 is running 67 Thread-2 is running 68 ...... 69 Thread-100 is running 70 主:53.294203758239746 n:0 71 ''' 72 73 #有的同學可能有疑問:既然加鎖會讓執行變成序列,那麼我在start之後立即使用join,就不用加鎖了啊,也是序列的效果啊 74 #沒錯:在start之後立刻使用jion,肯定會將100個任務的執行變成序列,毫無疑問,最終n的結果也肯定是0,是安全的,但問題是 75 #start後立即join:任務內的所有程式碼都是序列執行的,而加鎖,只是加鎖的部分即修改共享資料的部分是序列的 76 #單從保證資料安全方面,二者都可以實現,但很明顯是加鎖的效率更高. 77 from threading import current_thread,Thread,Lock 78 import os,time 79 def task(): 80 time.sleep(3) 81 print('%s start to run' %current_thread().getName()) 82 global n 83 temp=n 84 time.sleep(0.5) 85 n=temp-1 86 87 88 if __name__ == '__main__': 89 n=100 90 lock=Lock() 91 start_time=time.time() 92 for i in range(100): 93 t=Thread(target=task) 94 t.start() 95 t.join() 96 stop_time=time.time() 97 print('主:%s n:%s' %(stop_time-start_time,n)) 98 99 ''' 100 Thread-1 start to run 101 Thread-2 start to run 102 ...... 103 Thread-100 start to run 104 主:350.6937336921692 n:0 #耗時是多麼的恐怖 105 '''
相關推薦
Python學習【第23篇】:利用threading模組開執行緒 python併發程式設計之多執行緒1
python併發程式設計之多執行緒1 一多執行緒的概念介紹 threading模組介紹 threading模組和multiprocessing模組在使用層
Python學習【第21篇】:程序池以及回撥函式 python併發程式設計之多程序2-------------資料共享及程序池和回撥函式
python併發程式設計之多程序2-------------資料共享及程序池和回撥函式 一、資料共享 1.程序間的通訊應該儘量避免共享資料的方式 2.程序
Python學習【第19篇】:利用multiprocessing模塊開進程
http exceptio 返回 accept .html 介紹 init pipe 資源 Cpython支持的進程與線程 一、multiprocessing模塊介紹 python中的多線程無法利用CPU資源,在python中大部分情況使用多進程。
Python學習【第2篇】:Python之數據類型
msg ear sleep abc 命令 play bbbb 朋友 == 數字類型和字符串類型 1.bin()函數將十進制轉換成而進制 2.oct()函數將十進制轉換成八進制 3.hex()函數將十進制轉換成十六進制 十六進制表示:0-9 a b c
Python學習【第2篇】:Python之數據類型(2)
append 但是 iss 代碼 key 常用方法 uber ner ces 元組 #為何要有元組,存放多個值,元組不可變,更多的是用來做查詢 t=(1,[1,3],‘sss‘,(1,2)) #t=tuple((1,[1,3],‘sss‘,(1,2))) #
Python學習【第4篇】:元組魔法
vaule 根據 取值 保留 列表 tuple 樣書 key值 推薦 tu = (111,"xiaoxing",(11,22),[(33,44)],45,)#1.書寫格式#一般寫元組的時候推薦在最後加入逗號,#元組中的一級元素不可被修改,不能增加或者刪除print(tu)#
Python學習【第5篇】:數據類型和變量總結
style 不可變 nbsp 重新 class 數據 發現 舉例 convert 字符串,數字,列表,元組,字典 可變不可變 1.可變:列表 如: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; col
Python學習【第4篇】:Python之可變資料型別與不可變資料型別 可變資料型別和不可變資料型別
可變資料型別和不可變資料型別 1.可變資料型別:在id不變的情況下,value可改變(列表和字典是可變型別,但是字典中的key值必須是不可變型別) 2.不可變資料型別:value改變,id也跟著改變。
Python學習【第3篇】:Python之運算子 python-----運算子及while迴圈
python-----運算子及while迴圈 一、運算子 計算機可以進行的運算有很多種,不只是加減乘除,它和我們人腦一樣,也可以做很多運算。 種類:算術運
Python學習【第5篇】:Python之字元編碼問題 python之----------字元編碼具體原理
python之----------字元編碼具體原理 1.記憶體和硬碟都是用來儲存的。 CPU:速度快 硬碟:永久儲存 &nb
Python學習【第9篇】:Python之常用模組二(時間模組,序列化模組等) 常用模組2
常用模組2 一、time模組 表示時間的三種方式: 時間戳:數字(計算機能認識的) 時間字串:t='2012-12-12'
Python學習【第8篇】:Python之常用模組一(主要是正則以及collections模組) python--------------常用模組之正則
python--------------常用模組之正則 一、認識模組 什麼是模組:一個模組就是一個包含了python定義和宣告的檔案,檔名就是加上.py的字尾,但其實import載入的模組分為四個通用類別 :
Python學習【第15篇】:面向對象之異常處理
解決 tex over self. 代碼 pytho ice dig ans python------------------異常處理 一、錯誤與異常 程序中難免會出現錯誤,而錯誤分為兩種 1.語法錯誤:(這種錯誤,根本過不了python解釋器的語法
Python學習【第15篇】:面向物件之異常處理 python------------------異常處理
python------------------異常處理 一、錯誤與異常 程式中難免會出現錯誤,而錯誤分為兩種 1.語法錯誤:(這種錯誤,根本過不了pyt
Python學習【第20篇】:互斥鎖以及程序之間的三種通訊方式(IPC)以及生產者個消費者模型 python併發程式設計之多程序1-----------互斥鎖與程序間的通訊
python併發程式設計之多程序1-----------互斥鎖與程序間的通訊 一、互斥鎖 程序之間資料隔離,但是共享一套檔案系統,因而可以通過檔案來實現程序直接的通訊,
Python學習【第24篇】:死鎖,遞迴鎖,訊號量,Event事件,執行緒Queue python併發程式設計之多執行緒2------------死鎖與遞迴鎖,訊號量等
python併發程式設計之多執行緒2------------死鎖與遞迴鎖,訊號量等 一、死鎖現象與遞迴鎖 程序也是有死鎖的 所謂死鎖: 是指兩個或兩個以上
Python學習【第24篇】:死鎖,遞歸鎖,信號量,Event事件,線程Queue
個人 keyword 標準 性能測試 world src second gpo 機制 python並發編程之多線程2------------死鎖與遞歸鎖,信號量等 一、死鎖現象與遞歸鎖 進程也是有死鎖的 所謂死鎖: 是指兩個或兩個以上的進程或線程在執
Python學習【第20篇】:互斥鎖以及進程之間的三種通信方式(IPC)以及生產者個消費者模型
數量 release value body pan sin 回收 dom 之前 python並發編程之多進程1-----------互斥鎖與進程間的通信 一、互斥鎖 進程之間數據隔離,但是共享一套文件系統,因而可以通過文件來實現進程直接的通信,但問題
Python學習【第3篇】:Python之運算符
進行 com 結束 range 分享圖片 png microsoft mic pan 一、運算符 計算機可以進行的運算有很多種,不只是加減乘除,它和我們人腦一樣,也可以做很多運算。 種類:算術運算,比較運算,邏輯運算,賦值運算,成員運算,身份運算,位運算,今天我們先了解前四
mysql學習【第14篇】:pymysql pymysql模組
pymysql模組 一、安裝的兩種方法 第一種 #安裝 pip3 install pymysql 第二種