1. 程式人生 > >併發之多執行緒

併發之多執行緒

概念:執行緒是應用程式中工作的最小單元,或者又稱之為微程序。

組成:它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。

闡釋:執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。執行緒可以共享(呼叫)程序的資料資源

優點:共享記憶體,IO操作時候,創造併發操作

缺點:%^&%%^&$$(學了你就知道了,CPython個**的!)

 

關於多執行緒

多執行緒類似於同時執行多個不同程式,多執行緒執行有如下優點:

  • 使用執行緒可以把佔據長時間的程式中的任務放到後臺去處理。
  • 使用者介面可以更加吸引人,這樣比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
  • 程式的執行速度可能加快
  • 在一些等待的任務實現上如使用者輸入、檔案讀寫和網路收發資料等,執行緒就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如記憶體佔用等等。

執行緒在執行過程中與程序還是有區別的。每個獨立的執行緒有一個程式執行的入口、順序執行序列和程式的出口。但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。

每個執行緒都有他自己的一組CPU暫存器,稱為執行緒的上下文,該上下文反映了執行緒上次執行該執行緒的CPU暫存器的狀態。

指令指標和堆疊指標暫存器是執行緒上下文中兩個最重要的暫存器,執行緒總是在程序得到上下文中執行的,這些地址都用於標誌擁有執行緒的程序地址空間中的記憶體。

  • 執行緒可以被搶佔(中斷)。
  • 在其他執行緒正在執行時,執行緒可以暫時擱置(也稱為睡眠) -- 這就是執行緒的退讓。

執行緒可以分為:

  • 核心執行緒:由作業系統核心建立和撤銷。
  • 使用者執行緒:不需要核心支援而在使用者程式中實現的執行緒。

使用:

  • threading(推薦使用)

thread 模組已被廢棄。使用者可以使用 threading 模組代替。所以,在 Python3 中不能再使用"thread" 模組。為了相容性,Python3 將 thread 重新命名為 "_thread"。反正你不要再用就是了! 我習慣上用 from threading import Thread

Python中使用執行緒有兩種方式:函式或者用類來包裝執行緒物件。(跟程序差不多的)

Python3 通過兩個標準庫 _thread 和 threading 提供對執行緒的支援。

_thread 提供了低級別的、原始的執行緒以及一個簡單的鎖,它相比於 threading 模組的功能還是比較有限的。

threading 模組除了包含 _thread 模組中的所有方法外,還提供的其他方法:

  • threading.currentThread(): 返回當前的執行緒變數。
  • threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒。
  • threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果。

除了使用方法外,執行緒模組同樣提供了Thread類來處理執行緒,Thread類提供了以下方法:

  • run(): 用以表示執行緒活動的方法。
  • start():啟動執行緒活動。 
  • join([time]): 等待至執行緒中止。這阻塞呼叫執行緒直至執行緒的join() 方法被呼叫中止-正常退出或者丟擲未處理的異常-或者是可選的超時發生。
  • setDaemon(True):守護主執行緒,跟隨主執行緒退(必須要放在start()上方)
  • isAlive(): 返回執行緒是否活動的。
  • getName(): 返回執行緒名。
  • setName(): 設定執行緒名。

 

定義執行緒

import threading # 執行緒模組
import time
# 建立執行緒
def onepiece1(n):
    print("路飛正在使用橡膠火箭炮%s,攻擊力%s" %(time.ctime(),n))
    time.sleep(3)
    print("路飛結束該技能%s" %time.ctime())

def onepiece2(n):
    print("艾尼路正在出雷神萬擊%s你,攻擊力%s" %(time.ctime(),n))
    time.sleep(5)
    print("艾尼路結束該技能%s" %time.ctime())

if __name__ == '__main__':

    thread_1 = threading.Thread(target=onepiece1,args=(10,)) # 建立子執行緒
    thread_2 = threading.Thread(target=onepiece2,args=(9,))

    thread_1.start()
    # pyhton1.join()
    thread_2.start()
    thread_2.join() # 等待執行緒終止

    print("ending Fighting")
函式式
import threading
import time

class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):  # 定義每個執行緒要執行的函式
        print("running on number:%s" %self.num)
        time.sleep(3)
print("ending......")

if __name__ == '__main__':
    t1 = MyThread(1) # 繼承這個類,把1這個引數,傳給num ,t1就是個執行緒物件
    t2 = MyThread(2)
    t1.start()
    t2.start()
繼承類

GIL 

GIL 的存在是當初為了解決單核下多執行緒資料讀寫不安全的問題(主要保護直譯器裡的資料) 於是給每個程序加了互斥量 類似lock 但實現原理不一樣
lock是為了保護我們自己寫的程式用到的資料
但這導致後來出現多核cpu時,python不能在多核的時候實現真正的並行,,這些執行緒只會優先在一個核裡工作
這是屬於直譯器的弊病,可以解決但牽涉太多,成本太高了,而更換直譯器許多庫都不能使用,所以只能儘量避開這個影響:

繞過GIL的影響的辦法是:
計算密集的任務 使用多程序 io密集的 使用多執行緒,因為在io時,GIL會被釋放!

參考文章 https://blog.csdn.net/nawenqiang/article/details/79731858

執行緒互斥

多個執行緒共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正確性,需要對多個執行緒進行同步。

使用 Thread 物件的 Lock 和 Rlock 可以實現簡單的執行緒互斥。

這兩個物件都有 acquire 方法和 release 方法。

對於那些需要每次只允許一個執行緒操作的資料,可以將其操作放到 acquire 和 release 方法之間。

def sub():
    global num
    thread_lock_A.acquire()  # 用於執行緒同步
    tmep = num
    time.sleep(0.001)
    num = tmep - 1
    thread_lock_A.release()  # 釋放,開啟下一個執行緒
                             # 問題,lock之後100個執行緒就變為了序列執行,鎖內的程式碼
li = []
for i in range(100):
    t = threading.Thread(target=sub)
    t.start()
    li.append(t)

for t in li:
    t.join()
print("ending")
print(num)
執行緒互斥