多線程

線程

概述

通俗點講,在共享的空間里同時做不同的任務

進程和線程的區(qū)別

  1. 同一個進程中的所有線程共享同一內(nèi)存空間,但是2個進程之間內(nèi)存空間是獨立的。
  2. 同一個進程中的所有線程的數(shù)據(jù)是共享的,多個進程之間的數(shù)據(jù)是獨立的。
  3. 進程安全性高,線程安全性低
  4. 進程是資源分配的單元,線程是調(diào)度單元

python多線程編程

多線程修改全局變量

import time
from threading import Thread

"""多線程實現(xiàn)修改全局變量"""
# 定義一個全局變量
num = 0
print("num=", num)


def modify1():
    global num
    num += 1
    print('修改一次num')


def modify2():
    global num
    num += 1
    print('修改一次num')


def read():
    print("讀出線程,num的值是", num)


# 創(chuàng)建一個線程
t1 = Thread(target=modify1)
t2 = Thread(target=modify2)
t3 = Thread(target=read)
# 啟動線程
t1.start()
t2.start()
t3.start()
# 睡眠一秒,防止線程未執(zhí)行完就輸出num
time.sleep(1)
print("主線程,num的值是", num)

類繼承

from threading import Thread

"""簡單多線程類繼承"""


# 創(chuàng)建一個自己的多線程繼承類
class MyThread(Thread):
    def __init__(self, name):
        # 調(diào)用父類的構(gòu)造方法
        Thread.__init__(self)
        # super(MyThread, self).__init__()
        self.name = name

    def run(self):
        print("類繼承方式,我是", self.name)


# 實體化出來一個對象
myt1 = MyThread("江小白")
# 啟動線程,線程就去執(zhí)行類里的run方法
myt1.start()

線程的同步和互斥

臨界資源

多線程編程時主要考慮內(nèi)容:全局變量和代碼資源

全局變量訪問臨界區(qū)

from threading import Thread

global_variable = 0


# 線程1,寫數(shù)據(jù)
def write_data1():
    global global_variable
    for x in range(1000000):
        global_variable += 1
    print("線程1看到的global_variable:", global_variable)


# 線程2,寫數(shù)據(jù)
def write_data2():
    global global_variable
    for x in range(1000000):
        global_variable += 1
    print("線程2看到的global_variable:", global_variable)


# 主線程
def main():
    t1 = Thread(target=write_data1)
    t2 = Thread(target=write_data2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主線程里看到的global_variable:", global_variable)


if __name__ == '__main__':
    main()

代碼區(qū)訪問臨界資源

from threading import Thread

# 定義兩個全局變量
value1 = 0
value2 = 0


def writer_worker():
    global value1
    global value2
    count = 0
    while 1:
        count += 1
        # 使用兩句代碼改變那兩個全局變量
        value1 = count
        value2 = count


def check_worker():
    while 1:
        # 時間片輪算法導致value2 != value1,可以使用互斥鎖可解決這一問題
        if value2 != value1:
            print(f"value1:{value1}    value2:{value2}")


if __name__ == '__main__':
    t1 = Thread(target=writer_worker, name="寫線程")
    t2 = Thread(target=check_worker, name="讀線程")
    t1.start()
    t2.start()

如何解決訪問臨界資源

  1. 使用python內(nèi)置的原子結(jié)構(gòu)設計臨界資源。
    • list、dict的操作方法
    • queue.Queue的隊列結(jié)構(gòu)
  2. 使用Lock(鎖)、Semaphore(信號量)。
    • Lock對象(最常用的)
    • Event對象(事件對象)
    • Timer對象(定時器對象)

線程上的Lock對象

  • 鎖機制是操作系統(tǒng)底層提供的一種互斥訪問的機制。
  • 當一個任務獲取到未鎖定狀態(tài)的鎖時,任務繼續(xù)運行;
  • 當一個任務獲取到以鎖定狀態(tài)的鎖時,任務將被放入等待隊列,直到這把鎖被釋放時被喚醒。
  • threading提供了一個Lock類,來實現(xiàn)了這種鎖機制。

Lock代碼實例

import threading

value1 = 0
value2 = 0


def writer_worker():
    global value1
    global value2
    count = 0
    while True:
        count += 1
        # 上鎖
        # v_lock.acquire()
        with v_lock:
            value1 = count
            value2 = count
        # 解鎖
        # v_lock.release()


def check_worker():
    while True:
        # 上鎖
        v_lock.acquire()
        if value2 != value1:
            print(f"+++++v1:{value1}++v2:{value2}+++")
        # 解鎖
        v_lock.release()


if __name__ == '__main__':
    t1 = threading.Thread(target=writer_worker, name="寫線程")
    t2 = threading.Thread(target=check_worker, name="讀線程")
    # 買了一把鎖
    v_lock = threading.Lock()

    t1.start()
    t2.start()

線程共享數(shù)據(jù)(臨界數(shù)據(jù))安全問題解決

from threading import Thread, Lock

num = 0


# 定義兩個函數(shù)

def add1():
    global num
    # 上鎖 理論:鎖住最小的單元
    lock.acquire()
    for i in range(1000000):
        num += 1
        # 第一步:num
        # 第二步: temp = num+1
        # 第三步:num = temp
    # 解鎖
    lock.release()
    print(f"add1:{num}")

def add2():
    global num
    # 上鎖
    lock.acquire()
    for i in range(1000000):
        num += 1
    # 解鎖
    lock.release()
    print(f"add2:{num}")


# 買一把鎖
lock = Lock()
# 創(chuàng)建線程
t1 = Thread(target=add1)
t2 = Thread(target=add2)

# 啟動線程
t1.start()
t2.start()

# 等等線程結(jié)束
t1.join()
t2.join()
print(f"任務結(jié)束:{num}")

類似于事務:要么都成功,要么都失敗,就像同生共死那樣,保證數(shù)據(jù)的完整性

死鎖

  1. 產(chǎn)生原因:在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源并且同時等待對方的資源,就會造成死鎖。

  2. 產(chǎn)生結(jié)果:盡管死鎖很少發(fā)生,但一旦發(fā)生就會造成應用的停止響應。下面看一個死鎖的例子

  3. 解決方法:設置超時時間

# coding=utf-8
import threading
import time


class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name + '----do1---up----')
            time.sleep(1)

            # 設置超時時間
            if mutexB.acquire(blocking=True, timeout=2):
                print(self.name + '----do1---down----')
                mutexB.release()
            mutexA.release()


class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name + '----do2---up----')
            time.sleep(1)
            if mutexA.acquire():
                print(self.name + '----do2---down----')
                mutexA.release()
            mutexB.release()


mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

線程同步

利用鎖機制來解決線程同步,一旦同步,效率降低

import time
from threading import Thread, Lock


def f1():
    while True:
        if lock1.acquire():
            print(1)
            time.sleep(1)
            # 解開2號鎖
            lock2.release()


def f2():
    while True:
        if lock2.acquire():
            print(2)
            time.sleep(1)
            # 解開3號鎖
            lock3.release()


def f3():
    while True:
        if lock3.acquire():
            print(3)
            time.sleep(1)
            # 解開1號
            lock1.release()


# 鎖
lock1 = Lock()

# 鎖住2號
lock2 = Lock()
lock2.acquire()

# 鎖住3號
lock3 = Lock()
lock3.acquire()

# 創(chuàng)建三個線程
t1 = Thread(target=f1)
t2 = Thread(target=f2)
t3 = Thread(target=f3)

# 啟動線程
t1.start()
t2.start()
t3.start()

GIL

全局解釋鎖:為了保證線程的安全,在同一時刻,只有一個線程被CPU執(zhí)行。

解決方案:調(diào)用C

分析什么時候使用多進程和多線程

多進程:CPU密集型,需要CPU參與大量運算

多線程:IO密集型 ,執(zhí)行這個上任務的時候,CPU只需要很少,其它大部分時間都傳輸,文件傳輸

再加一條,如果你不知道你的代碼到底算CPU密集型還是IO密集型,教你個方法:

multiprocessing這個module有一個dummy的sub module,它是基于multithread實現(xiàn)了multiprocessing的API。

假設你使用的是multiprocessing的Pool,是使用多進程實現(xiàn)了concurrency
from multiprocessing import Pool
如果把這個代碼改成下面這樣,就變成多線程實現(xiàn)concurrency
from multiprocessing.dummy import Pool

兩種方式都跑一下,哪個速度快用哪個就行了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 進程和線程 進程 所有運行中的任務通常對應一個進程,當一個程序進入內(nèi)存運行時,即變成一個進程.進程是處于運行過程中...
    勝浩_ae28閱讀 5,267評論 0 23
  • 又來到了一個老生常談的問題,應用層軟件開發(fā)的程序員要不要了解和深入學習操作系統(tǒng)呢? 今天就這個問題開始,來談談操...
    tangsl閱讀 4,334評論 0 23
  • 進程和線程 進程 所有運行中的任務通常對應一個進程,當一個程序進入內(nèi)存運行時,即變成一個進程.進程是處于運行過程中...
    小徐andorid閱讀 2,996評論 3 53
  • 本文首發(fā)于我的個人博客:尾尾部落 本文是我刷了幾十篇一線互聯(lián)網(wǎng)校招java后端開發(fā)崗位的面經(jīng)后總結(jié)的多線程相關(guān)題目...
    繁著閱讀 2,143評論 0 7
  • 1.內(nèi)存的頁面置換算法 (1)最佳置換算法(OPT)(理想置換算法):從主存中移出永遠不再需要的頁面;如無這樣的...
    杰倫哎呦哎呦閱讀 3,613評論 1 9

友情鏈接更多精彩內(nèi)容