併發編程-多線程

多線程

什麼是線程:

線程指的是一條流水線的工做過程的總稱python

線程是CPU的基本執行單位安全

​ 對比進程而言,進程僅僅是一個資源單位其包含了程序運行所需的資源,就像一個車間多線程

​ 而單有資源是沒法生產出產品的,必須有具體的生產產品的邏輯代碼併發

​ 線程就至關於車間中的一條流水線,而你的代碼就是流水線上的一道道工序ui

特色:

​ 1.每一個進程都會有一個默認的線程spa

​ 2.每一個進程能夠存在多個線程操作系統

​ 3.同一進程中的全部線程之間數據是共享的線程

​ 4.建立線程的開銷遠比建立進程小的多code

image-20190307060333482

主線程與子線程的區別:

​ 1.線程之間是沒有父子之分,是平等的對象

​ 2.主線程是由操做系統自動開啓的,而子線是由程序主動開啓

​ 3.即時主線程的代碼執行完畢,也不會結束進程,會等待全部線程執行完畢,進程才結束

開啓線程的兩種方式:

1.實例化Tread類,target參數用於指定子線程要執行的任務

from threading import  Thread

def task():
    print("子線程 run........")

t = Thread(target=task)
t.start()
print("over")

2.繼承Tread類,覆蓋run方法

from threading import  Thread

class MyThread(Thread):
    def run(self):
        print("子線程 run........")

t = MyThread()
t.start()
print("over")

與進程在使用方法上沒有任何區別,不一樣的是開啓子線程的代碼能夠寫在任意位置

之因此使用方法徹底相同是由於,多進程實際上是爲了彌補多線程的缺憾而誕生的。詳見GIL鎖

線程與進程區別:

1.同一進程中 線程之間數據共享

a = 100
def task():
    global a
    print("子線程 run........")
    a = 1

t = Thread(target=task)
t.start()

print(a) # 1
print("over")

2.建立線程的開銷遠比建立進程小的多

from threading import  Thread
from multiprocessing import  Process
import time

def task():
    pass

if __name__ == '__main__':
    start = time.time()
    for i in range(100):
        p = Thread(target=task)
        p.start()
    print(time.time()-start)
# 修改Thread 爲Process類 查看結果

3.不管開啓了多少子線程PID是不會變的

from threading import  Thread
import os

def task():
    print(os.getpid())

for i in range(100):
    p = Thread(target=task)
    p.start()

Tread類的經常使用屬性:

# threading模塊包含的經常使用方法
import threading
print(threading.current_thread().name) #獲取當前線程對象
print(threading.active_count()) # 獲取目前活躍的線程數量
print(threading.enumerate()) # 獲取全部線程對象


t = Thread(name="aaa")
# t.join() # 主線程等待子線程執行完畢
print(t.name) # 線程名稱
print(t.is_alive()) # 是否存活
print(t.isDaemon()) # 是否爲守護線程

守護線程:

設置守護線程的語法與進程相同,相同的是也必須放在線程開啓前設置,不然拋出異常。

守護線程的特色:

​ 守護線程會在被守護線程結束後當即結束

from threading import  Thread
import time

def task():
    print("start......")
    time.sleep(5)
    print("end......")

t = Thread(target=task)
# t.setDaemon(True)
t.daemon = True
t.start()
print("main over!")

疑惑:

from threading import  Thread
import time

def task():
    print("start....1")
    time.sleep(3)
    print("end......1")

def task2():
    print("start....2")
    time.sleep(4)
    print("end......2")

t = Thread(target=task)
t.daemon = True
t.start()

t2 = Thread(target=task2)
t2.start()

print("main over!")

打印main over後主線程代碼執行完畢,可是守護線程t1並無當即結束,這是什麼緣由呢?

答:主線程會等待全部子線程執行完畢後結束

​ 在上述例子中,一共有三個線程,主線程 ,t1,t2

​ 雖然t1是守護線程 ,可是t2並非因此主線程會等待t2執行結束才結束

​ 順序是:守護線程 等待 主線程 等待 其他子線程

換句話說,守護線程會隨着全部非守護線程結束而結束。

線程鎖

互斥鎖

多線程的最主要特徵之一是:同一進程中全部線程數據共享

一旦共享必然出現競爭問題。

a = 10
#lock = Lock()
def task():
    global a
    #lock.acquire()
    b = a - 1
    time.sleep(0.1)
    a = b
    #lock.release()
for i in  range(10):
    t = Thread(target=task)
    t.start()

for t in threading.enumerate():
    if t != threading.current_thread():
        t.join()
print(a)
# 輸出 9

當多個線程要併發修改同一資源時,也須要加互斥鎖來保證數據安全。

一樣的一旦加鎖,就意味着串行,效率必然下降。

死鎖

現有兩把鎖l1和l2 用於表示盤子和筷子

兩個線程的目標是吃飯,要吃飯的前提是同時拿到筷子和盤子,可是兩我的的目標不一樣一個先拿筷子 ,一個先拿盤子最終形成死鎖

l1 = Lock()
l2 = Lock()

def task():
    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")
    time.sleep(0.1)
    l2.acquire()
    print(threading.current_thread().name, "拿到了盤子")

    print("吃飯")
    l1.release()
    l2.release()

def task2():
    l2.acquire()
    print(threading.current_thread().name, "拿到了盤子")

    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")

    print("吃飯")

    l2.release()
    l1.release()

t1 = Thread(target=task)
t1.start()
t2 = Thread(target=task2)
t2.start()

共有兩把鎖,可是一人拿到了一把,而且互不釋放,相互等待,致使程序卡死,這就死鎖。

要發生死鎖只有兩種狀況

​ 1.有不止一把鎖,不一樣線程或進程分別拿到了不一樣的鎖不放

​ 2.對同一把鎖執行了屢次acquire

其中第二種狀況咱們能夠經過可重入鎖來解決

可重入鎖

Rlock 同一個線程能夠屢次執行acquire,釋放鎖時,有幾回acquire就要release幾回。

可是本質上同一個線程屢次執行acquire時沒有任何意義的,其餘線程必須等到RLock所有release以後才能訪問共享資源。

因此Rlock僅僅是幫你解決了代碼邏輯上的錯誤致使的死鎖,並不能解決多個鎖形成的死鎖問題

# 同一把RLock 屢次acquire
#l1 = RLock()
#l2 = l1

# 不一樣的RLock 依然會鎖死
#l1 = RLock()
#l2 = RLock()

def task():
    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")
    time.sleep(0.1)
    l2.acquire()
    print(threading.current_thread().name, "拿到了盤子")

    print("吃飯")
    l1.release()
    l2.release()

def task2():
    l2.acquire()
    print(threading.current_thread().name, "拿到了盤子")

    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")

    print("吃飯")

    l2.release()
    l1.release()

t1 = Thread(target=task)
t1.start()
t2 = Thread(target=task2)
t2.start()

忠告:在處理併發安全時 用完公共資源後必定要釋放鎖

信號量

Semaphore

信號量也是一種鎖,其特殊之處在於可讓一個資源同時被多個線程共享,並控制最大的併發訪問線程數量。

​ 若是把Lock比喻爲家用洗手間,同一時間只能一我的使用。

​ 那信號量就能夠看作公共衛生間,同一時間能夠有多我的同時使用。

from threading import  Thread,Semaphore,current_thread
import time

s = Semaphore(3)
def task():
    s.acquire()
    print("%s running........" % current_thread())
    time.sleep(1)
    s.release()
    
for i in range(20):
    Thread(target=task).start()

相關文章
相關標籤/搜索