您好,登錄后才能下訂單哦!
一多線程的概念介紹
threading模塊介紹
threading模塊和multiprocessing模塊在使用層面,有很大的相似性。
二、開啟多線程的兩種方式
1.創建線程的開銷比創建進程的開銷小,因而創建線程的速度快
from multiprocessing import Process
from threading import Thread
import os
import time
def work():
print('<%s> is running'%os.getpid())
time.sleep(2)
print('<%s> is done'%os.getpid())
if name == 'main':
t=Thread(target=work,)
t.start()
print('主',os.getpid())
開啟進程的第一種方式
from threading import Thread
import time
class Work(Thread):
def init(self,name):
super().init()
self.name = name
def run(self):
print('%s say hell'%self.name)
if name == 'main':
t = Work('egon')
t.start()
print('主')
開啟線程的第二種方式(用類)
在一個進程下開啟多個線程與在一個進程下開啟多個子進程的區別
from multiprocessing import Process
from threading import Thread
import time
def work():
time.sleep(2)
print('hello')
if name == 'main':
t = Thread(target=work)#如果等上幾秒,他會在開啟的過程中先打印主,如果不等會先打印hello
t.start()
print('主')
線程的開啟速度大于進程的開啟速度
from multiprocessing import Process
from threading import Thread
import os
def work():
print('hello',os.getpid())
if name == 'main':
#在主進程下開啟多個線程,每個線程都跟主進程的pid一樣
t1= Thread(target=work)
t2 = Thread(target=work)
t1.start()
t2.start()
print('主線程pid',os.getpid())
#來多個進程,每個進程都有不同的pid
p1 = Process(target=work)
p2 = Process(target=work)
p1.start()
p2.start()
print('主進程pid', os.getpid())
在同一個進程下開多個進程和開多個線程的pid的不同
from threading import Thread
from multiprocessing import Process
import os
def work():
global n
n-=1
print(n) #所以被改成99了
n = 100
if name == 'main':
p = Thread(target=work) #當開啟的是線程的時候,因為同一進程內的線程之間共享進程內的數據
#所以打印的n為99
p.start()
p.join()
print('主',n) #毫無疑問子進程p已經將自己的全局的n改成了0,
# 但改的僅僅是它自己的,查看父進程的n仍然為100
同一進程內的線程共享該進程的數據
進程之間是互相隔離的,不共享。需要借助第三方來完成共享(借助隊列,管道,共享數據)
三、練習
練習一:多線程實現并發
from socket import
from threading import Thread
s = socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口重用
s.bind(('127.0.0.1',8081))
s.listen(5)
print('start running...')
def talk(coon,addr):
while True: # 通信循環
try:
cmd = coon.recv(1024)
print(cmd.decode('utf-8'))
if not cmd: break
coon.send(cmd.upper())
print('發送的是%s'%cmd.upper().decode('utf-8'))
except Exception:
break
coon.close()
if name == 'main':
while True:#鏈接循環
coon,addr = s.accept()
print(coon,addr)
p =Thread(target=talk,args=(coon,addr))
p.start()
s.close()
服務端
from socket import
c = socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))
while True:
cmd = input('>>:').strip()
if not cmd:continue
c.send(cmd.encode('utf-8'))
data = c.recv(1024)
print('接受的是%s'%data.decode('utf-8'))
c.close()
客戶端
練習二:三個任務,一個接收用戶輸入,一個將用戶輸入的內容格式化成大寫,一個將格式化后的結果存入文件
from threading import Thread
import os
input_l = []
format_l = []
def talk(): #監聽輸入任務
while True:
cmd = input('>>:').strip()
if not cmd:continue
input_l.append(cmd)
def format():
while True:
if input_l:
res = input_l.pop()#取出來
format_l.append(res.upper()) #取出來后變大寫
def save():
while True:
if format_l: #如果format_l不為空
with open('db','a') as f:
f.write(format_l.pop()+'\n') #寫進文件
f.flush()
if name == 'main':
t1=Thread(target=talk)
t2=Thread(target=format)
t3=Thread(target=save)
t1.start()
t2.start()
t3.start()
答案
四、多線程共享同一個進程內的地址空間
function(){ //MT4教程:www.kaifx.cn/mt4.html
from threading import Thread
from multiprocessing import Process
import os
n = 100
def talk():
global n
n-=100
print(n)
if name == 'main':
t = Thread(target=talk) #如果開啟的是線程的話,n=0
t.start()
t.join()
print('主',n)
五、線程對象的其他屬性和方法
Thread實例對象的方法
threading模塊提供的一些方法:
from threading import Thread
from multiprocessing import Process
import time,os,threading
def work():
time.sleep(2)
print('%s is running' % threading.currentThread().getName())
print(threading.current_thread()) #其他線程
print(threading.currentThread().getName()) #得到其他線程的名字
if name == 'main':
t = Thread(target=work)
t.start()
print(threading.current_thread().getName()) #主線程的名字
print(threading.current_thread()) #主線程
print(threading.enumerate()) #連同主線程在內有兩個運行的線程
time.sleep(2)
print(t.is_alive()) #判斷線程是否存活
print(threading.activeCount())
print('主')
線程的其他屬性和方法
六、join與守護線程
主進程等所有的非守護的子進程結束他才結束(回收它子進程的資源):(有父子關系)
主線程等非守護線程全都結束它才結束: (沒父子關系)
from threading import Thread
import time,os
def talk():
time.sleep(3)
print('%s is running..'%os.getpid())
if name == 'main':
t = Thread(target=talk)
t.start()
t.join() #主進程在等子進程結束
print('主')
join
守護線程與守護進程的區別
1.守護進程:主進程會等到所有的非守護進程結束,才銷毀守護進程。也就是說(主進程運行完了被守護的那個就干掉了)
2.守護線程:主線程運行完了守護的那個還沒有干掉,主線程等非守護線程全都結束它才結束
from multiprocessing import Process
from threading import Thread,currentThread
import time,os
def talk1():
time.sleep(2)
print('hello')
def talk2():
time.sleep(2)
print('you see see')
if name == 'main':
t1 = Thread(target=talk1)
t2 = Thread(target=talk2)
# t2 = Process(target=talk2)
t1.daemon = True
t1.start()
t2.start()
print('主線程',os.getpid())
守護進程和守護線程
#3 --------迷惑人的例子
from threading import Thread
import time
def foo():
print(123)
time.sleep(2) #如果這個等的時間小于下面等的時間,就把end123也打印了
print('end123')
def bar():
print(456)
time.sleep(10)
print('end456')
if name == 'main':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True #主線程運行完了守護的那個還沒有干掉,
t1.start()
t2.start()
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早期版本的遺留問題。
from threading import Thread,Lock
import time
n=100
def work():
mutex.acquire()
global n
temp=n
time.sleep(0.01)
n=temp-1
mutex.release()
if name == 'main':
mutex=Lock()
t_l=[]
s=time.time()
for i in range(100):
t=Thread(target=work)
t_l.append(t)
t.start()
for t in t_l:
t.join()
print('%s:%s' %(time.time()-s,n))
全局解釋鎖
鎖通常被用來實現對共享資源的同步訪問。為每一個共享資源創建一個Lock對象,當你需要訪問該資源時,調用acquire方法來獲取鎖對象(如果其它線程已經獲得了該鎖,則當前線程需等待其被釋放),待資源訪問完后,再調用release方法釋放鎖:
import threading
mutex = threading.Lock()
mutex.aquire()
'''
對公共數據的操作
'''
mutex.release()
鎖的格式
1 分析:
2 2 1.100個線程去搶GIL鎖,即搶執行權限
3 3 2. 肯定有一個線程先搶到GIL(暫且稱為線程1),然后開始執行,一旦執行就會拿到lock.acquire()
4 4 3. 極有可能線程1還未運行完畢,就有另外一個線程2搶到GIL,然后開始運行,但線程2發現互斥鎖lock還未被線程1釋放,于是阻塞,被迫交出執行權限,即釋放GIL
5 5 4.直到線程1重新搶到GIL,開始從上次暫停的位置繼續執行,直到正常釋放互斥鎖lock,然后其他的線程再重復2 3 4的過程
如果不加鎖:并發執行,速度快,數據不安全。
加鎖:串行執行,速度慢,數據安全。
#不加鎖:并發執行,速度快,數據不安全
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 #耗時是多么的恐怖
'''
互斥鎖與join的區別(重點!!!)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。