併發編程之多線程

来源:https://www.cnblogs.com/Hybb/archive/2019/09/13/11512011.html
-Advertisement-
Play Games

併發編程 併發(偽):由於執行速度特別快,人感覺不到 並行(真):創建10個人同時操作 線程 1. 單進程,單線程的應用程式 print('666') 2. 到底什麼是線程?什麼是進程 Python自己沒有這玩意,Python中調用的操作系統的線程和進程(偽線程) 3. 多線程 工作的最小單元 共用 ...


併發編程

  • 併發(偽):由於執行速度特別快,人感覺不到
  • 並行(真):創建10個人同時操作

線程

  1. 單進程,單線程的應用程式
    • print('666')
  2. 到底什麼是線程?什麼是進程
    • Python自己沒有這玩意,Python中調用的操作系統的線程和進程(偽線程)
  3. 多線程
    • 工作的最小單元
    • 共用進程中所有資源
    • 每個線程可以分擔一點任務,最終完成最後的結果

python多線程原理:python的多線程實際是一個假的多線程(多個線程在1核CPU上運行,進行快速的切換導致錯覺為同時執行)

代碼

import threading
def func(arg):
    print(arg)
    
th = threading.Thread(target=func)
th.start()
print('end')

一個應用程式:軟體

  • 預設一個程式只有一個進程
  • 可以有多個進程(預設只有一個),一個進程可以創建多個線程(預設一個)。

Python多線程情況下:

  • 計算密集型操作:效率低(GIL鎖)
  • IO操作:效率高

Python多進程情況下:

  • 計算密集型操作:效率高(浪費空間)
  • IO操作:效率高(浪費資源)

以後寫Python時:

  • IO密集型用多線程:文件/輸入輸出/socket
  • 計算密集型用多進程:

進程

  • 獨立開闢記憶體
  • 進程之間的數據隔離

註意:進程是為了提供環境讓線程工作

Python中線程和進程(GIL鎖)

GIL鎖,全局解釋器鎖。用於限制一個進程中同一個時刻只有一個線程被CPU調度

擴展:預設GIL鎖在執行100個cpu指令後(過期時間)。

線程的使用

多線程基本使用

  • 基礎例子
import threading
def func(arg):
    print(arg)
    
th = threading.Thread(target=func)
th.start()
print('end')
  • 測試例子(主線程預設會先執行自己的代碼,然後等子線程執行完畢後,才會結束)
import time
import threading

def func(arg):
    time.sleep(arg)
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
t1.start()

t2 = threading.Thread(target=func,args=(9,))
t2.start()

print('end')
  • jojn方法:
import time
import threading

def func(arg):
    time.sleep(3)
    print(arg)

print('開始執行t1')
t1 = threading.Thread(target=func,args=(3,))
t1.start()
# 無參:讓主線程等待,等到子線程t1執行完畢後,才能往下執行
# 有參:讓主線程在這裡最多等待n秒,無論執行完畢與否,會繼續往下走
t1.join(2)

print('開始執行t2')
t2 = threading.Thread(target=func,args=(9,))
t2.start()
t2.join()   # 讓主線程等待,等到子線程t1執行完畢後,才能往下執行

print('end')
  • 線程名稱獲取
import threading

def func(arg):
    # 獲取當前執行該函數的線程的對象
    t = threading.current_thread()
    # 根據當前線程對象,獲取當前線程名稱
    name = t.getName()
    print(name,arg)

t1 = threading.Thread(target=func,args=(3,))
t1.setName('mhy')
t1.start()

t2 = threading.Thread(target=func,args=(9,))
t2.setName('zz')
t2.start()

print('end')
  • 線程本質
# 先列印3還是end?
import threading

def func(arg):
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
# start是開始線程嘛?不是
# start是告訴cpu,我已準備就緒,你可以調度我了。
t1.start()

print('end')
  • 面向對象式子多線程使用
import threading
class MyThread(threading.Thread):
    def run(self):
        print(123,self._args,self._kwargs)


t1 = MyThread(args=(11,))
t1.start()

t2 = MyThread(args=(12,))
t2.start()
  • 計算密集型多線程(作用不大)
import threading
def func(lis,num):
    et = [i+num for i in lis]
    print(et)

t1 = threading.Thread(target=func,args=([11,22,33],1))
t1.start()

t2 = threading.Thread(target=func,args=([44,55,66],100))
t2.start()
  • 實例代碼
import threading
import time

#定義類繼承Thread線程
class CodingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('正在寫代碼%s' % threading.current_thread())
            time.sleep(1)

class DrawingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('正在畫圖%s' % threading.current_thread())
            time.sleep(1)

def main():
    t1 = CodingThread()
    t2 = DrawingThread()

    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

線程安全(Lock)

  • 線程安全,多線程操作時,內部會讓所有線程排隊處理,如:list/dict/Queue
  • 線程不安全 + 人(LOCK) = 排隊處理

簡介:

Python自帶的解釋器是CPython。CPython解釋器的多線程實際上是一個假的多線程(在多核CPU中,只能利用一核,不能利用多核)。同一時刻只有一個線程在執行,為了保證同一時刻只有一個線程在執行,在CPython解釋器中有一個東西叫做GIL(Global Intepreter Lock),叫做全局解釋器鎖。這個解釋器鎖是有必要的。因為CPython解釋器的記憶體管理不是線程安全的。當然除了CPython解釋器,還有其他的解釋器,有些解釋器是沒有GIL鎖的,見下麵:

  1. Jython:用Java實現的Python解釋器。不存在GIL鎖。
  2. IronPython:用.net實現的Python解釋器。不存在GIL鎖。
  3. PyPy:用Python實現的Python解釋器。存在GIL鎖。

GIL雖然是一個假的多線程。但是在處理一些IO操作(比如文件讀寫和網路請求)還是可以在很大程度上提高效率的。在IO操作上建議使用多線程提高效率。在一些CPU計算操作上不建議使用多線程,而建議使用多進程。

不安全例子

import time
import threading

lit = []
def func(arg):
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)  # m和arg應該是一個值

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

鎖機制原理

多線程同時執行,會導致數據同時執行,獲取數據不符合結果。

線程先獲得執行權時,將會將執行過程鎖起來,其他線程不能使用,必須要等該線程執行完,其他線程才可獲取執行權,執行線程,將解決數據不統一的方法

Lock 鎖(一次放行一個)

import time
import threading

lit = []
lock = threading.Lock()  # 創建一個鎖

def func(arg):
    lock.acquire() # 將該行代碼以下代碼鎖起來,直到遇到release釋放
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)
    lock.release()  # 釋放鎖

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

RLock 遞歸鎖(一次放行多個),

因為lock鎖如果有多層鎖機制,會造成死鎖現象,所以有了Rlock(遞歸鎖)

代碼如下:

import time
import threading

lit = []
lock = threading.RLock()  # 創建一個遞歸鎖

def func(arg):
    # Lock只能解開一個鎖,會造成死鎖線程
    # RLock可以解開兩個鎖,解決了下麵問題
    lock.acquire() # 將該行代碼以下代碼鎖起來,直到遇到release釋放
    lock.acquire()
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)
    lock.release()  # 釋放鎖
    lock.release()  # 釋放鎖

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

BoundedSemaphore(一次放N個)信號量

import time
import threading

# 創建一個鎖,這個可以支持你同時進行幾次鎖,預設為1次
lock = threading.BoundedSemaphore(3)

def func(arg):
    lock.acquire() 
    print(arg)
    time.sleep(1)
    lock.release()

for num in range(20):
    t = threading.Thread(target=func,args=(num,))
    t.start()

Condition(1次放x個數)動態輸入

Lock版本的生產者與消費者模式可以正常的運行,但是存在一個不足,在消費者中,總是通過While True死迴圈並且上鎖的方式去判斷錢夠不夠,上鎖是一個很耗費CPU資源的行為,因此這種方式不是最好的,還有一種更好的方式便是使用Threading.condition來實現,threading.condition可以在沒有數據的時候處於堵塞等待狀態,一旦有合適的數據了,還可以使用notify相關的函數來通知其他處於等待狀態的線程。這樣可以不用做一些無用的上鎖和解鎖的操作。可以提高程式的性能。首先對threading.Condition相關的函數做個介紹,threading.Condition類似threading.Lock,可以在修改全局數據的時候進行上鎖,也可以在修改完畢後進行解鎖。以下將一些常用的函數做個簡單的介紹:

  1. acquire:上鎖
  2. release:解鎖
  3. wait:將當前線程處於等待狀態,並且會釋放鎖。可以被其他線程使用notify和notify_all函數喚醒。被喚醒後繼續等待上鎖,上鎖後執行下麵的代碼。
  4. notify:通知某個等待的線程,預設是第1個等待的線程。
  5. notify_all:通知正在等待的線程。notify和notify_all不會釋放鎖。並且需要在release之前調用。
# 方法一
import time
import threading

# 創建一個鎖,這個可以支持你同時進行幾次鎖,預設為1次
lock = threading.Condition()

def func(arg):
    print('線程進來了')
    lock.acquire()
    lock.wait()

    print(arg)
    time.sleep(1)
    lock.release()

for num in range(3):
    t = threading.Thread(target=func,args=(num,))
    t.start()

while True:
    num = int(input('>>>:'))
    lock.acquire()
    lock.notify(num)
    lock.release()
    
# 方法二
import time
import threading

# 創建一個鎖,這個可以支持你同時進行幾次鎖,預設為1次
lock = threading.Condition()

def xxx():
    print('來執行函數了')
    input('>>>:')
    return True

def func(arg):
    print('線程進來了')
    lock.wait_for(xxx)
    print(arg)
    time.sleep(1)

for num in range(3):
    t = threading.Thread(target=func,args=(num,))
    t.start()

event(事件)1次放所有

import threading

# 創建一個鎖,這個可以支持你同時進行幾次鎖,預設為1次
lock = threading.Event()

def func(arg):
    print('線程進來了')
    lock.wait()  # 變紅燈
    print(arg)

for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

input('>>>')
lock.set()   # 變綠燈
input('>>>')

# 重新回歸 紅燈狀態
lock.clear()
for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

input('>>>')
lock.set()   # 變綠燈
input('>>>')

線程總結

線程安全:列表和字典就是線程安全;

為什麼要加鎖?

  • 非線程安全
  • 控制一段代碼的時候,每次只能最多同時執行幾個

threading.local

為每一個線程創建一個字典鍵值對,為數據進行隔離

示例代碼:

import time
import threading

pond = threading.local()

def func(arg):
    # 內部會為當前線程創建一個空間用於存儲,phone = 自己的值 ,將數據隔離開
    pond.phone = arg 
    time.sleep(2)
    # 取當前線程自己空間取值
    print(pond.phone,arg)  

for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

線程池

使用concurrent來創建線程池,使用線程池可以有效的解決線程無止境的問題,用戶每一次一個請求都會創建一個線程,這樣線程一堆積也會造成程式緩慢,所以,線程池就可以幫我們解決這個問題,線程池可以設定,最多同時執行n個線程,由自己設定。

示例代碼:

import time
from concurrent.futures import ThreadPoolExecutor

# 創建一個線程池,(最多同時執行5個線程)
pool = ThreadPoolExecutor(5)

def func(arg1,arg2):
    time.sleep(1)
    print(arg1,arg2)

for num in range(5):
    # 去線程池申請一個線程,讓線程執行func函數
    pool.submit(func,num,8)

Queue線程安全隊列:

線上程中,訪問一些全局變數,加鎖是一個經常的過程。如果你是想把一些數據存儲到某個隊列中,那麼Python內置了一個線程安全的模塊叫做queue模塊。Python中的queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先進先出)隊列Queue,LIFO(後入先出)隊列LifoQueue。這些隊列都實現了鎖原語(可以理解為原子操作,即要麼不做,要麼都做完),能夠在多線程中直接使用。可以使用隊列來實現線程間的同步。相關的函數如下:

  1. 初始化Queue(maxsize):創建一個先進先出的隊列。
  2. qsize():返回隊列的大小。
  3. empty():判斷隊列是否為空。
  4. full():判斷隊列是否滿了。
  5. get():從隊列中取最後一個數據。
  6. put():將一個數據放到隊列中。

示例代碼

 #encoding:utf-8

from queue import Queue
import threading
import time

# q = Queue(4)  # 添加4個隊列
# q.put(10)   #第1個隊列插入一條數據
# q.put(4)    #第2個隊列插入一條數據

# for x in range(4):
#     q.put(x)
#
# for x in range(4):
#     print(q.get())
# print(q.empty())
# print(q.full())
# print(q.qsize())

def set_value(q):
    index = 0
    while True:
        q.put(index)
        index +=1
        time.sleep(2)
def get_value(q):
    while True:
        print(q.get())

def main():
    q = Queue(4)
    t1 = threading.Thread(target=set_value,args=[q])
    t2 = threading.Thread(target=get_value,args=[q])

    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

生產者消費者模型

模型三部件

  • 生產者
    • 隊列:先進先出
    • 棧:後進先出
  • 消費者
  • 隊列

生產者和消費者模型解決了 不用一直等待的問題

使用Queue創建隊列

示例代碼:

import time
import threading
from queue import Queue

q = Queue()

def producer(id):
    '''生產者'''
    while True:
        time.sleep(2)
        q.put('包子')
        print('廚師 %s 生產了一個包子'%id)

def consumer(id):
    '''消費者'''
    while True:
        time.sleep(1)
        v1 = q.get()
        print('顧客 %s 吃了一個包子'%id)

for produce in range(1,4):
    t1 = threading.Thread(target=producer,args=(produce,))
    t1.start()

for consu in range(1,3):
    t2 = threading.Thread(target=consumer,args=(consu,))
    t2.start()

總結:

  1. 操作系統幫助開發者操作硬體
  2. 程式員寫好代碼在操作系統上運行(依賴解釋器)
  3. 任務特別多。以前一個一個執行,(串列),現在可以使用多線程

為什麼創建線程?

  • 由於線程是cpu工作的最小單元,創建線程可以利用多核優勢實現並行操作(java,C#)

為啥創建進程?

  • 進程和進程之間做數據隔離(java/C)

Python

  • Python中存在一個GIL鎖。
    • 造成:多線程無法利用多核優勢
    • 解決:開多進程處理(浪費資源)
    • 總結:
      • IO密集型:多線程
      • 計算密集型:多進程
  • 線程的創建
    • Thread
    • 面向對象繼承(Threading.Thread)
  • 其他
    • jojn
    • setDeanon
    • setName
    • threading.current_thread()
    • 獲得
    • 釋放

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • BFC的生成 在實現CSS的佈局時,假設我們不知道BFC的話,很多地方我們生成了BFC但是不知道.在佈局中,一個元素是block元素還是inline元素是必須要知道的.而BFC就是用來格式化塊狀元素盒子,同樣還有管理內連盒子的IFC等.那首先就來瞭解一下什麼是FC. 既然BFC是一塊獨立的渲染區域, ...
  • JSP三大指令 一個jsp頁面中,可以有0~N個指令的定義! 1. page --> 最複雜:<%@page language="java" info="xxx"...%> * pageEncoding和contentType: > pageEncoding:它指定當前jsp頁面的編碼,只要不說謊, ...
  • mutation是更改Vuex的store中的狀態的唯一方法,mutation類似於事件註冊,每個mutation都可以帶兩個參數,如下: state ;當前命名空間對應的state payload ;傳入的參數,一般是一個對象 創建Vuex.Store()倉庫實例時可以通過mutations創建每 ...
  • 【需求:】數據從競品網站爬過來,經過分析處理之後,把結果通過網頁實時反饋給業務人員。 【應用:】2個應用: 一個是爬取數據的應用:不斷從競品網站爬數據,每次爬到的數據為一批。然後,對每一批爬到的數據進行清洗和分析,生成唯一批次號(batch_no),將分析結果持久化入庫。 一個是展示頁面:實時刷新持 ...
  • 單例模式:對於類的單例模式設計,就是採取一定的方法保證在整個軟體系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法(靜態方法)。 單例模式有8種方式: 1、餓漢式(靜態常量) // 2、餓漢式(靜態代碼塊) 3、懶漢式(線程不安全) 4、懶漢式(線程安全,同步方法) 5、懶 ...
  • 前言 今天講的是結構型設計模式中的最後一個,這個模式也就是代理模式,在前段時間我寫的一篇關於正向代理和反向代理的文章。雖說此代理非彼代理。但是代理一詞還是具有相似的含義的。這裡我們繼續使用文章中的代購一個例子來講述一下代理模式吧,人不方便去購買哪些物品,這時就有一個中間人,他來購買。他代替我去購買。 ...
  • 1.nfs實現的原理解析? 2.安裝、配置、nfs服務 1.安裝 2.配置 3.根據配置進行初始化環境 4.啟動 5.客戶端測試 6.錯誤的示範 7.多個客戶端共用一個存儲伺服器 (NFS) 8.實現開機自動掛載(因為伺服器不重啟) 擴展瞭解即可 3.nfs相關的配置參數 4.rw 和 ro 2.驗 ...
  • 如何界定爬蟲的合法性,我通過翻閱大量文章、事件、分享、司法案例,總結出界定的三個關鍵點 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...