Python多執行緒程式設計,執行緒鎖,以及補充上一篇多程序文章
程序補充
程序間的訊號
訊號是唯一的非同步通訊方法
一個程序向另一個程序傳送一個訊號來傳遞某種資訊,接受者根據傳遞的資訊來做相應的事
$ kill -l
檢視系統訊號說明
$ kill -9 pid號
對程序傳送訊號
訊號名稱 | 說明 |
---|---|
1) SIGHUP | 連線斷開 |
2) SIGINT | ctrl+c |
3) SIGQUIT | ctrl+\ |
20) SIGTSTP | ctrl+z |
9) SIGKILL | 終止程序 |
19) SIGSTOP | 暫停程序 |
26) SIGVTALRM | 時鐘訊號 |
17) SIGCHLD | 子程序退出時給父程序發的訊號 |
在Python中import signal
可以獲取訊號
os.kill(pid, sig)
- 功能:傳送訊號
- 引數
- pid:要傳送訊號的PID號
- sig :訊號名稱
import os import signal os.kill(12345,signal.SIGKILL) #殺死程序
signal.alarm(time)
個人理解:把傳送訊號的資訊告知系統核心,應用層程式繼續執行,時間到之後利用核心告知應用層程式進行處理
- 功能:非阻塞函式,向自身程序傳送一個時鐘訊號
- 引數:time->整型時間秒
import signal import time signal.alarm(3) #3秒後向自身傳送一個時鐘訊號 while True: time.sleep(1) print("等待時鐘訊號") '''列印結果 等待時鐘訊號 等待時鐘訊號 鬧鐘 '''
signal.alarm(3) #3秒後向自身傳送一個時鐘訊號 time.sleep(2) signal.alarm(5) #程序只有一個時鐘訊號,第二個會覆蓋上面的時鐘訊號 while True: time.sleep(1) print("等待時鐘訊號"
signal.pause()
- 功能:阻塞程序,然後等待訊號
signal.signal(signum, handler)
功能:處理訊號
引數
signum:要處理的訊號
handler:訊號的處理方法
SIG_DFL
表示使用預設方法處理SIG_IGN
表示忽略這個訊號function
表示傳入一個函式,用指定的函式處理def function(sig, frame)
sig:捕獲到的訊號
frame:訊號物件
import signal from time import sleep signal.alarm(5) # 5秒後向自身傳送一個時鐘訊號 # 使用訊號的預設方法處理 # signal.signal(signal.SIGALRM,signal.SIG_DFL) # 忽略時鐘訊號 # signal.signal(signal.SIGALRM,signal.SIG_IGN) # 忽略Ctrl+c訊號 # signal.signal(signal.SIGINT,signal.SIG_IGN) while True: sleep(2) print("等待時鐘...")
# 使用自定義函式處理訊號 import signal from time import sleep def fun1(sig, frame): if sig == signal.SIGALRM : print("接收到時鐘訊號") elif sig == signal.SIGINT : print("ctrl+c就不結束") signal.alarm(5) # 5秒後向自身傳送一個時鐘訊號 # 使用自定義函式處理訊號 # 處理時鐘訊號 signal.signal(signal.SIGALRM,fun1) # 處理ctrl+c訊號 signal.signal(signal.SIGINT,fun1) while True: print("等待") sleep(2) '''列印結果 等待 等待 等待 接收到時鐘訊號 等待 ... '''
訊號量(訊號燈)
原理:給定一個數量對多個程序可見,且多個程序都可以操作,程序可以對數量多少的判斷執行各自的行為
from multiprocessing import Semaphore
sem = Semaphore(num)
- 功能:建立訊號量
- 引數:訊號量的初始值
- 返回值:訊號量的物件
sem.get_value()
:獲取訊號量的值sem.acquire()
:將訊號量 -1,當訊號為0時會阻塞sem.release()
:將訊號量 +1
from multiprocessing import Semaphore, Process
# 建立訊號量物件
sem = Semaphore(num)
def fun():
print("程序%d等待訊號量"%os.getpid())
# 消耗一個訊號量
sem.acquire()
print("程序%d消耗訊號量"%os.getpid())
# 新增一個訊號量
sem.release()
print("程序%d新增訊號量"%os.getpid())
jobs = []
for i in range(4):
p = Process(target = 4)
jobs.append(p)
p.start()
for i in jobs:
i.join()
print(sem.get_value())
程序的同步互斥
臨界資源:多個程序或者執行緒都能操作的共享資源
臨界區:操作臨界區資源的程式碼段
同步:同步是一種合作關係,為完成某個任務,多程序或者多執行緒之間形成的一種協調關係
互斥:互斥是一種制約關係,
Event事件
from multiprocessing import Event
e = Event()
:建立一個事件物件e.wait([timeout])
:設定事件阻塞e.set()
:事件設定,當事件被設定後e.wait()
不再阻塞,等於釋放資源區e.clear()
:清除設定,當事件被設定e.clear()
後,e.wait()
又會阻塞,阻塞資源區e.is_set()
:事件狀態判斷,判斷事件是否處於被設定的狀態
from multiprocessing import Event
# 建立事件物件
e = Event()
# 檢視
print(e.is_set()) # False
e.set()
print(e.is_set()) # True
e.wait(3)
print(e.is_set()) # True
e.clear()
print(e.is_set()) # False
from multiprocessing import Event,Process
from time import sleep
def wait_event1():
print("1想操作臨界區資源")
e.wait()
print("1開始操作臨界區資源",e.is_set())
with open("file") as f:
print(f.read())
def wait_event2():
print("2也想操作臨界區資源")
# 超時3秒檢測
e.wait(3)
# 判斷是否被設定
if e.is_set():
print("2開始操作臨界區資源",e.is_set())
with open("file") as f:
print(f.read())
else:
print("2不能操作")
# 建立事件物件
e = Event()
p1 = Process(target = wait_event1)
p2 = Process(target = wait_event2)
p1.start()
p2.start()
print("主程序操作")
with open("file",'w') as f:
f.write("HELLO WORD")
# 延遲4秒釋放臨界區
sleep(4)
# 釋放臨界區資源
e.set()
print("釋放臨界區")
p1.join()
p2.join()
Lock 鎖
from multiprocessing import Lock
lock = Lock()
:建立一個鎖物件lock.acquire()
:上鎖,如果已經是上鎖狀態,呼叫此函式會阻塞lock.release()
:解鎖
from multiprocessing import Lock,Process
import sys
def writer1():
# 上鎖
lock.acquire()
for i in range(20):
sys.stdout.write("writer1111\n")
# 解鎖
lock.release()
def writer2():
# 上鎖
lock.acquire()
for i in range(20):
sys.stdout.write("writer2222\n")
# 解鎖
lock.release()
lock = Lock()
w1 = Process(target = writer1)
w2 = Process(target = writer2)
w1.start()
w2.start()
w1.join()
w2.join()
第二種方法
使用with
語句上鎖,with語句執行完畢後會自動解鎖
with lock:
.....
.....
多執行緒
什麼是執行緒?
執行緒也是一種多工的程式設計方法,可以利用計算機多核資源完成程式的併發執行。
執行緒又被稱為輕量級程序
執行緒的特徵
執行緒是計算機多核分配的最小單位
一個程序可以包含多個執行緒
- 執行緒也是一個執行的過程,消耗計算機資源,多個執行緒共享程序的資源和空間
- 執行緒的建立刪除消耗的資源都遠遠比程序小
- 多個執行緒之間執行互不干擾
- 執行緒也有自己的特有屬性,比如指令集ID
threading 模組建立執行緒
t=threading.Thread()
功能:建立執行緒物件
引數
name:執行緒名稱,如果為空則為預設值,Tread-1,Tread-2,Tread-3
- target:執行緒函式
- args:元組,給執行緒函式按照位置傳參
- kwargs:字典,給縣城函式按照鍵值傳參
t.start()
:啟動執行緒,自動執行執行緒函式t.join([timeout])
:回收程序t.is_alive()
:檢視執行緒狀態t.name()
:檢視執行緒名稱t.setName()
:設定執行緒名稱t.daemon屬性
:預設主線成退出不影響分支執行緒繼續執行,如果設定為True則分支執行緒 隨著主執行緒一起退出設定方法
t.daemon = True
t.setDaemon(Ture)
#!/usr/bin/env python3 from threading import Thread from time import sleep import os # 建立執行緒函式 def music(): sleep(2) print("分支執行緒") t = Thread(target = music) # t.start() # ****************************** print("主執行緒結束---------") '''沒有設定的列印結果 主執行緒結束--------- 分支執行緒 ''' '''設定為True列印結果 主執行緒結束--------- '''
threading.currentThread
:獲取當前執行緒物件
此處程式碼示意子執行緒共享同一個程序內的變數
#!/usr/bin/env python3
from threading import Thread
from time import sleep
import os
# 建立執行緒函式
def music():
global a
print("a=",a)
a = 10000
for i in range(5):
sleep(1)
print("1212")
a = 1
t = Thread(target = music)
t.start()
t.join()
print("主執行緒的a =",a)
建立自己的執行緒類
考察點:類的使用,呼叫父類的__init__
方法,函式傳參和*傳參
from threading import Thread
import time
class MyThread(Thread):
name1 = 'MyThread-1'
def __init__(self,target,args=(), kwargs={}, name = 'MyThread-1'):
super().__init__()
self.name = name
self.target = target
self.args = args
self.kwargs = kwargs
def run(self):
self.target(*self.args,**self.kwargs)
def player(song,sec):
for i in range(2):
print("播放 %s:%s"%(song,time.ctime()))
time.sleep(sec)
t =MyThread(target = player, args = ('亮亮',2))
t.start()
t.join()
執行緒通訊
通訊方法:由於多個執行緒共享程序的記憶體空間,所以執行緒間通訊可以使用全域性變數完成
注意事項:執行緒間使用全域性變數往往要同步互斥機制保證通訊的安全
執行緒同步互斥方法
event
e = threading.Event()
:建立事件物件e.wait([timeout])
:設定狀態,如果已經設定,那麼這個函式將阻塞,timeout為超時時間e.set
:將e變成設定狀態e.clear
:刪除設定狀態import threading from time import sleep def fun1(): print("bar拜山頭") global s s = "天王蓋地虎" def fun2(): sleep(4) global s print("我把限制解除了") e.set() # 解除限制,釋放資源 def fun3(): e.wait() # 檢測限制 print("說出口令") global s if s == "天王蓋地虎": print("寶塔鎮河妖,自己人") else: print("打死他") s = "哈哈哈哈哈哈" # 建立同步互斥物件 e = threading.Event() # 建立新執行緒 f1 = threading.Thread(target = fun1) f3 = threading.Thread(target = fun3) f2 = threading.Thread(target = fun2) # 開啟執行緒 f1.start() f3.start() f2.start() #準備回收 f1.join() f3.join() f2.join()
執行緒鎖
lock = threading.Lock()
:建立鎖物件lock.acquire()
:上鎖lock.release()
:解鎖
也可以用過with
來上鎖
with lock:
...
...
@需要了解!!!
Python執行緒的GIL問題(全域性直譯器):
python—->支援多執行緒—->同步互斥問題—->加鎖解決—->超級鎖(給直譯器加鎖)—->直譯器同一時刻只能解釋一個執行緒—>導致效率低下
後果:
一個直譯器同一時刻只能解釋執行一個執行緒,所以導致Python執行緒效率低下,但是當遇到IO阻塞時執行緒會主動讓出直譯器,因此Pyhton執行緒更加適合高延遲的IO程式併發
解決方案
- 儘量使用程序完成併發(和沒說一樣)
- 不適當用C直譯器 (用C# ,JAVA)
- 儘量使用多種方案組合的方式進行併發操作,執行緒用作高延遲IO