python 多線程threading lock condition Queen

提高效率

一個人幹活沒有兩個人幹活快,也就是說多線程是爲了更好的幫助程序執行提高效率。

python多線程

總體來說,在python中,實際上並不提倡使用多線程來提高效率,或是說不提倡在CPython中使用多線程,這是因爲在python設計之初的時候,那個年代只有單核cpu,由於硬件發展速度過快,最初設計的爲了保護數據的一種機制,GIL(global interpreter lock),簡單來說就是在python程序執行的開始,底層會鎖住這段程序,以防止數據的混亂,關門,不讓其他程序參與進來,這種機制在硬件發展飛速的今天,成爲了一個遺留問題。

  • Cpython應對這種機制辦法:
  1. 使用多進程 mutiprocess模塊,可以解決,比較喫cpu資源
  2. 使用其他語言編寫多線程模塊(c語言等),調用api
  3. 分佈式架構,使用多個虛擬環境同時幹活(Docker)
  4. 捨棄Cpython解釋器,使用IornPython或是JavaPython
  • cpython多線程使用注意事項
  1. 在使用cpu密集型的代碼時,不建議使用,建議多進程
  2. 在IO密集型的代碼時,還是可以提高一點效率的。

示例

不使用多線程程序

import time


def func(n):
    print("函數開始執行", time.ctime())
    time.sleep(n)
    print("函數執行結束", time.ctime())


def main():
    print("主函數開始執行", time.ctime())
    func(2)
    func(3)
    print("主函數執行結束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函數開始執行 Tue Oct 29 13:34:08 2019
函數開始執行 Tue Oct 29 13:34:08 2019
函數執行結束 Tue Oct 29 13:34:10 2019
函數開始執行 Tue Oct 29 13:34:10 2019
函數執行結束 Tue Oct 29 13:34:13 2019
主函數執行結束 Tue Oct 29 13:34:13 2019
"""

"""
在本次程序中,代碼從上到下依次執行,func()第一次執行完畢後,第二次纔開始執行
所以本次程序一共花費了5秒
"""

使用多線程

import time
import threading

def func(n):
    print(f"{threading.current_thread().name}-函數開始執行", time.ctime())
    time.sleep(n)
    print(f"{threading.current_thread().name}函數執行結束", time.ctime())


def main():
    print("主函數開始執行", time.ctime())
    t1 = threading.Thread(target=func, args=(3,))
    t2 = threading.Thread(target=func, args=(2,))
    t1.start()
    t2.start()
    print("主函數執行結束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函數開始執行 Tue Oct 29 13:55:41 2019
Thread-1-函數開始執行 Tue Oct 29 13:55:41 2019
Thread-2-函數開始執行 Tue Oct 29 13:55:41 2019
主函數執行結束 Tue Oct 29 13:55:41 2019
Thread-2函數執行結束 Tue Oct 29 13:55:43 2019
Thread-1函數執行結束 Tue Oct 29 13:55:44 2019
"""

"""
本次程序主函數執行花費了0秒,主函數都結束了,但是線程還沒有結束,
這顯然不符合我們的預期,所以,這時候我們需要主函數等待線程執行完畢以後,
在繼續執行。所以就用到了join()方法。
"""

join()

import time
import threading

def func(n):
    print(f"{threading.current_thread().name}-函數開始執行", time.ctime())
    time.sleep(n)
    print(f"{threading.current_thread().name}函數執行結束", time.ctime())


def main():
    print("主函數開始執行", time.ctime())
    t1 = threading.Thread(target=func, args=(3,))
    t2 = threading.Thread(target=func, args=(2,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主函數執行結束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函數開始執行 Tue Oct 29 13:58:33 2019
Thread-1-函數開始執行 Tue Oct 29 13:58:33 2019
Thread-2-函數開始執行 Tue Oct 29 13:58:33 2019
Thread-2函數執行結束 Tue Oct 29 13:58:35 2019
Thread-1函數執行結束 Tue Oct 29 13:58:36 2019
主函數執行結束 Tue Oct 29 13:58:36 2019
"""

"""
本次程序主函數執行花費了3秒,線程1花費了3秒,線程2花費了3秒
得出線程1與線程2是同時執行的。

注意:相比於不使用多線程的時候,省了兩秒的時間。
"""

lock()

在上面程序中我們只是看到了時間確實是省了下來,但是因爲並沒有涉及到數據,因此我們現在測試,多線程對數據的影響,儘量不要讓線程對全局變量操作。

不上鎖的情況

因爲我們是使用隨機數來模擬線程的執行,所以不能考慮執行效率

import random
import time
import threading

"""
我們現在使用兩個進程操作同一個數據元素,比如列表,我們希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一個進程則會添加兩遍,但是爲了效率,我們使用兩個線程同時添加,我們使用time.sleep來模擬線程的動作,
比如說有些線程搶佔的比較快,有些搶佔比較慢。

"""

l1 = []
lock = threading.Lock()
def func(l):

    with lock:
        for i in range(5):
            time.sleep(random.randint(0,2))
            l.append(i)

def main():
    print("主函數開始執行", time.ctime())
    t1 = threading.Thread(target=func, args=(l1,))
    t2 = threading.Thread(target=func, args=(l1,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(l1)
    print("主函數執行結束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函數開始執行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函數執行結束 Tue Oct 29 14:28:04 2019

"""

"""
從上面可以看出,出現的結果比不是我們想要的【0,1,2,3,4,0,1,2,3,4】
這就是因爲線程的執行順序是無序的。所以門要想控制他們的順序得到我們想要的結果,
這時候lock()就上場了。
"""

上鎖的情況

import random
import time
import threading

"""
我們現在使用兩個進程操作同一個數據元素,比如列表,我們希望l1依次添加(0,1,2,3,4,0,1,2,3,4)
一個進程則會添加兩遍,但是爲了效率,我們使用兩個線程同時添加,我們使用time.sleep來模擬線程的動作,
比如說有些線程搶佔的比較快,有些搶佔比較慢。

"""

l1 = []
lock = threading.Lock()   # 創建鎖對象
def func(l):
    with lock:
        for i in range(5):
            time.sleep(random.randint(0,2))
            l.append(i)

def main():
    print("主函數開始執行", time.ctime())
    t1 = threading.Thread(target=func, args=(l1,))
    t2 = threading.Thread(target=func, args=(l1,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(l1)
    print("主函數執行結束", time.ctime())


if __name__ == '__main__':
    main()

"""
主函數開始執行 Tue Oct 29 14:27:56 2019
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
主函數執行結束 Tue Oct 29 14:28:04 2019

"""

"""
這次我們可以看出輸出符合我們的預期
"""

生產者消費者模式

import random
import threading
import time

gmoney = 1000    # 定義總錢數
lock = threading.Lock()  # 創建鎖對象
gTotalTimes = 10    # 生產總次數
gTime = 0           # 記錄生產次數

class Producer(threading.Thread):
    def run(self):
        global gmoney
        global gTime
        while True:
            with lock:
                money = random.randint(100, 1000)
                if gTime >= 10:         # 當生產了10次,結束程序
                    break
                gmoney += money
                print(f"{threading.current_thread().name}生產了{money},總剩餘{gmoney}")
                gTime += 1
                time.sleep(1)
                
class Consumer(threading.Thread):
    def run(self):
        global gmoney
        while True:
            with lock:
                money = random.randint(100, 1000)
                if gmoney >= money:
                    gmoney -= money
                    print(f"{threading.current_thread().name}消費了{money},總剩餘{gmoney}")
                    time.sleep(1)
                else:
                    if gTime >= gTotalTimes:   # 當生產者不生產的時候,結束
                        break
                    
def main():
    for x in range(3):
        t = Consumer(name=f"消費者線程{x}")
        t.start()

    for x in range(5):
        t = Producer(name=f"生產者線程{x}")
        t.start()


if __name__ == '__main__':
    main()
    
"""
消費者線程0消費了629,總剩餘371
消費者線程0消費了138,總剩餘233
消費者線程0消費了188,總剩餘45
消費者線程0消費了351,沒錢了
消費者線程0消費了390,沒錢了
消費者線程2消費了722,沒錢了
消費者線程2消費了572,沒錢了
生產者線程1生產了462,總剩餘507
生產者線程1生產了695,總剩餘1202
生產者線程1生產了333,總剩餘1535
生產者線程4生產了834,總剩餘2369
生產者線程4生產了244,總剩餘2613
生產者線程4生產了606,總剩餘3219
生產者線程4生產了592,總剩餘3811
生產者線程4生產了277,總剩餘4088
生產者線程4生產了422,總剩餘4510
生產者線程4生產了345,總剩餘4855
消費者線程1消費了925,總剩餘3930
消費者線程1消費了844,總剩餘3086
消費者線程1消費了967,總剩餘2119
消費者線程1消費了277,總剩餘1842
消費者線程1消費了808,總剩餘1034
消費者線程1消費了878,總剩餘156
消費者線程1消費了117,總剩餘39
"""

condition模式生產者消費者模式

lock上鎖解鎖比較消耗cpu資源,condition就是優化這種模式的
threading.Condition()繼承於threading.Lock()
常用函數說明:

  • acquire:上鎖
  • release:解鎖
  • wait:將當前線程處於等待狀態,並且會釋放鎖,可以被其他線程使用notify和notify_all函數喚醒,被喚醒後,會繼續等待上鎖,上鎖後繼續執行下面的代碼
  • notify:通知某個正在等待的線程,默認是第一個等待的線程
  • notify_all:通知所有正在等待的線程,notify和notify_all不會釋放鎖,並且需要在release之前調用。
"""
生產者與消費者操作同一個全局變量,gmoney,生產者生產,消費者消費,
當gmoney不足時,就掛起(wait()),等待生產者生產,
當生產者生產完畢以後,就通知(notify_all())消費者可以繼續消費。
相比於LOCK()頻繁上鎖,更節省CPU資源
"""
import random
import threading
import time

gmoney = 1000
condition = threading.Condition()
gTotalTimes = 10
gTime = 0



class Producer(threading.Thread):
    def run(self):
        global gmoney
        global gTime
        while True:
            money = random.randint(100, 1000)
            condition.acquire()
            if gTime >= gTotalTimes:
                condition.release()
                break
            gmoney += money
            print(f"{threading.current_thread().name}生產了{money},總剩餘{gmoney}")
            gTime += 1
            condition.notify_all()
            condition.release()
            time.sleep(1)



class Consumer(threading.Thread):
    def run(self):
        global gmoney
        while True:
            money = random.randint(100, 1000)
            condition.acquire()
            while gmoney <= money:
                if gTime >= gTotalTimes:
                    condition.release()
                    return
                print(f"{threading.current_thread().name}準備消費{money},不足")
                condition.wait()
            gmoney -= money
            print(f"{threading.current_thread().name}消費了{money},剩餘{gmoney}")

            condition.release()
            time.sleep(1)



def main():
    for x in range(3):
        t = Consumer(name=f"消費者線程{x}")
        t.start()

    for x in range(5):
        t = Producer(name=f"生產者線程{x}")
        t.start()


if __name__ == '__main__':
    main()

"""
消費者線程0消費了100,剩餘900
消費者線程1消費了667,剩餘233
消費者線程2準備消費368,不足
生產者線程0生產了981,總剩餘1214
生產者線程1生產了526,總剩餘1740
消費者線程2消費了368,剩餘1372
生產者線程2生產了777,總剩餘2149
生產者線程3生產了915,總剩餘3064
生產者線程4生產了925,總剩餘3989
消費者線程0消費了453,剩餘3536
消費者線程1消費了982,剩餘2554
生產者線程1生產了893,總剩餘3447
生產者線程2生產了492,總剩餘3939
生產者線程0生產了522,總剩餘4461
消費者線程2消費了801,剩餘3660
生產者線程3生產了562,總剩餘4222
生產者線程4生產了500,總剩餘4722
消費者線程1消費了889,剩餘3833
消費者線程0消費了693,剩餘3140
消費者線程2消費了423,剩餘2717
消費者線程0消費了876,剩餘1841
消費者線程1消費了559,剩餘1282
消費者線程2消費了674,剩餘608
消費者線程1消費了592,剩餘16
"""

Queen

線程安全隊列
常用函數說明:

  • Queen(maxsize):創建一個先進先出的隊列
  • qsize():返回隊列的大小
  • empty():判斷是否爲空
  • full():判斷是否滿了
  • get():取出隊列最後一個數據
  • put():見你那個一個數據放到隊列中

可以避免線程爭搶

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章