1. 程式人生 > >Python多執行緒程式設計,執行緒鎖,以及補充上一篇多程序文章

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:
    .....
    .....

多執行緒

  • 什麼是執行緒?

    1. 執行緒也是一種多工的程式設計方法,可以利用計算機多核資源完成程式的併發執行。

    2. 執行緒又被稱為輕量級程序

  • 執行緒的特徵

    1. 執行緒是計算機多核分配的最小單位

    2. 一個程序可以包含多個執行緒

    3. 執行緒也是一個執行的過程,消耗計算機資源,多個執行緒共享程序的資源和空間
    4. 執行緒的建立刪除消耗的資源都遠遠比程序小
    5. 多個執行緒之間執行互不干擾
    6. 執行緒也有自己的特有屬性,比如指令集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