1. 程式人生 > 其它 >併發程式設計之執行緒

併發程式設計之執行緒

目錄

併發程式設計之執行緒

一 什麼是執行緒

	計算機相當於大工廠,工廠裡有一個個車間(程序) ,有很多人(執行緒) 幹不同的事。

	**程序是資源分配的最小單位,執行緒是CPU排程的最小單位。每一個程序中至少有一個執行緒。**

	執行緒的開銷更小,更輕量級。

二 開啟執行緒的兩種方式

1 通過函式的方式

# 第一種,通過函式
from threading import Thread
import time

def task():
    print('start >>>>')
    time.sleep(1)
    print('end >>>>>')

if __name__ == '__main__':
    t = Thread(target=task)  # 例項化得到一個物件
    t.start()  # 物件.start()啟動執行緒
    print('>>>> main <<<<')

2 通過類繼承的方式

# 第二種,通過類繼承的方式
from threading import Thread
import time

class MyThread(Thread):
    def run(self) -> None:
        print('start >>>>')
        time.sleep(1)
        print('end >>>>>')

if __name__ == '__main__':
    t = MyThread()  # 若有引數,則需要定義類的__init__()方法
    t.start()
    print('>>>> main <<<<')

三 執行緒物件join方法

1 作用:等待子執行緒執行結束

2 程式碼示例:

# join的使用
import time
from threading import Thread

def task(n):
    print('start >>>>')
    time.sleep(n)
    print('end >>>>>')

if __name__ == '__main__':
    t = Thread(target=task,args=(2,))
    t.start()
    t1 = Thread(target=task,args=(3,))
    t1.start()

    t.join()  # 等待子執行緒t執行結束再執行後續程式碼
    print('>>>> main <<<<')

四 同一個程序下的多個執行緒資料共享

​ 同一程序內的執行緒之間共享程序內的資料。

# 多執行緒下資料共享
from threading import Thread
import time

money = 999
def task(n):
    global money
    money = n
    print('start >>>>')
    # time.sleep(1)
    print('end >>>>>')

if __name__ == '__main__':
    t = Thread(target=task,args=(2,))
    t.start()

    t1 = Thread(target=task,args=(666,))
    t1.start()
    # t.join()
    # t1.join()
    print(money)
    print('>>>> main <<<<')
"""
start >>>>
end >>>>>
start >>>>
end >>>>>
666
>>>> main <<<<
"""

五 執行緒物件及其他方法

1 重要的方法

1) t.name  				# 檢視執行緒名字
2) t.getName()  		# 檢視執行緒名字
3) active_count()  		# 檢視當前程序下還有多少執行緒存活
4) t.is_alive()  		# 檢視當前執行緒是否存活
5) t.ident  			# 當作是執行緒的id號
6) current_thread()  	# 相當於執行緒物件本身

2 程式碼示例

# 程序的其他方法
from threading import Thread, current_thread, active_count
import os, time

def task(n):
    print('start >>>>')
    print(current_thread().name)  # 執行緒名字
    print(os.getpid())  # 程序號
    time.sleep(n)
    print('end >>>>>')

if __name__ == '__main__':
    t1 = Thread(target=task, name='lxx', args=(2,))
    t2 = Thread(target=task, args=(8,))
    t1.start()
    t2.start()
    t1.join()
    print('---------', t1.is_alive())
    print('---------', t2.is_alive())    
    print('*********', t1.ident)  # 當作程序的id號
    print('*********', t2.ident)
    print(os.getpid())
    print(active_count())  # 打印出2 ,開了兩個執行緒,還有一個主執行緒,但是t1被join()阻塞已執行完畢
"""
start >>>>
lxx
23656
start >>>>
Thread-1
23656
end >>>>>
--------- False
--------- True
********* 16060
********* 27292
23656
2
end >>>>>
"""

六 守護執行緒

1 守護的原則

**無論是程序還是執行緒,都遵循:守護xxx會等待主xxx執行完畢後被銷燬。**

2 注意:需要強調的是,執行完畢並非終止執行

#### (1) 對主程序來說:
	執行完畢指的是主程序程式碼執行完畢;
	**詳細解釋:**主程序在其他程式碼結束後就已經算執行完畢了(守護程序在此時就被回收) ,然後主程序會一直等非守護的子程序都執行完畢後回收子程序的資源(否則會產生殭屍程序) ,才會結束;

#### (2) 對主執行緒來說
	執行完畢指的是主執行緒所在的程序內所有非守護執行緒統統執行完畢,主執行緒才算執行完畢。
	**詳細解釋:**主執行緒在其他非守護執行緒執行完畢後才算執行完畢(守護執行緒在此時就被回收) 。因為主執行緒的結束意味著程序的結束,程序整體的資源都將被回收,而程序必須保證非守護執行緒都執行完畢後才能結束。

3 程式碼示例:

# 守護執行緒
from threading import Thread, active_count
import time

def task(n):
    print('start >>>>')
    time.sleep(n)
    print('---------', active_count())
    print('end >>>>>')

if __name__ == '__main__':
    t1 = Thread(target=task, name='lxx', args=(10,))
    t1.daemon = True
    # t1.setDaemon(True)
    t1.start()
    t2 = Thread(target=task, name='lxx', args=(4,))
    t2.start()
    print('>>>> main <<<<')
"""
start >>>>
start >>>>
>>>> main <<<<
--------- 3
end >>>>>
"""

七 執行緒鎖互斥

from threading import Thread, Lock
import time

money = 99

def task(mutex):
    global money
    mutex.acquire()  # 在修改資料的時候加鎖
    temp = money
    time.sleep(0.1)

    money = temp -1
    mutex.release()  # 修改完畢後,釋放鎖,其他執行緒就能再次搶到鎖

if __name__ == '__main__':
    lll = []
    mutex = Lock()
    for i in range(10):  # 模擬是個執行緒,都進行修改的操作
        t = Thread(target=task,args=(mutex,))
        t.start()
        lll.append(t)

    for i in lll:  # 十個執行緒都進行阻塞呼叫,以得出最終的結果
        i.join()

    print(money)

八 GIL全域性直譯器鎖理論

1 前提

1).python的直譯器有很多,cpython,jpython,pypy(python寫的直譯器);

2).python的庫多,庫都是基於cpython寫起來的,其他直譯器沒有那麼多的庫;

3).cpython中有一個全域性大鎖,每條執行緒要執行,必須獲取到這個鎖;

2 為何會有GIL全域性直譯器鎖

	為了實現python的垃圾回收機制;

3 GIL全域性直譯器鎖的缺陷

1).某個執行緒想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python程序中,GIL只有一個。拿不到通行證的執行緒,就不允許進入CPU執行;

2).導致CPython中的多執行緒其實就是單執行緒;

4 總結

**1.python的多執行緒,不能利用多核優勢**
	cpython直譯器中有一個全域性鎖(GIL) ,執行緒必須獲取到GIL才能執行,我們開的多執行緒,不管有幾個cpu,同一時刻,只有一個執行緒在執行。

**2.針對CPython直譯器:**
    ①如果是io密集型操作:開多執行緒;

    ②如果是計算密集型:開多程序。