1. 程式人生 > 實用技巧 >Response——下載檔案、重定向

Response——下載檔案、重定向

一:執行緒理論

一臺計算機相當於一個工廠,工廠裡有很多車間(程序),車間裡面有很多工人(執行緒)。

真正幹活兒的是工人(執行緒)。

1.什麼是執行緒

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

程序是資源分配的最小單位,執行緒是CPU排程的最小單位執行緒開銷更小,更輕量級

2.執行緒的資料是共享的

在作業系統中,每一個程序的記憶體空間是獨立的,資料不共享

但是同一個程序中的各個執行緒資料是共享的。

二:開啟執行緒的2種方式

1.第一種 - 普通方式

from threading import Thread
import time


def task(t_name, s_time):
    print(f'子執行緒[{t_name}]開始')
    time.sleep(s_time)
    print(f'子執行緒[{t_name}]結束')


if __name__ == '__main__':
    t1 = Thread(target=task, args=('t1', 1))  # 例項化得到一個執行緒物件
    t1.start()  # 物件.start()啟動執行緒
    print('主執行緒')


# 子執行緒[t1]開始
# 主執行緒
# 子執行緒[t1]結束

2.第二種 - 繼承類的方式

from threading import Thread
import time


class MyThread(Thread):
    def run(self):
        print('子執行緒開始')
        time.sleep(1)
        print('子執行緒結束')


if __name__ == '__main__':
    t = MyThread()
    t.start()
    print('主執行緒')


# 子執行緒開始
# 主執行緒
# 子執行緒結束

三:TCP服務端實現併發效果

To be continue...

四:執行緒物件join方法

from threading import Thread
import time


def task(t_name, s_time):
    print(f'{t_name} 開始')
    time.sleep(s_time)
    print(f'{t_name} 結束')


if __name__ == '__main__':
    t1 = Thread(target=task, args=('t1', 1))
    t2 = Thread(target=task, args=('t2', 2))

    t1.start()
    t2.start()

    t1.join()  # 等待子執行緒執行結束
    t2.join()

    print('主執行緒')


# t1 開始
# t2 開始
# t1 結束
# t2 結束
# 主執行緒

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

from threading import Thread
import time

money = 99


def task(t_name, n):
    global money
    print(f'{t_name} 開始')
    money = n
    print(f'{t_name} 結束')


if __name__ == '__main__':
    t1 = Thread(target=task, args=('t1', 10))
    t2 = Thread(target=task, args=('t2', 100))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print(money)
    print('主執行緒')
    

# t1 開始
# t1 結束
# t2 開始
# t2 結束
# 100
# 主執行緒

六:執行緒物件 及 其他方法

from threading import Thread, current_thread, active_count
import time


def task():
    print('子執行緒開始')
    print(current_thread().name)  # 打印出當前執行緒名字
    time.sleep(1)
    print('子執行緒結束')


if __name__ == '__main__':
    t1 = Thread(target=task, name='I\'m Thread1')
    t2 = Thread(target=task, name='I\'m Thread2')
    t1.start()
    t2.start()
    print(f'存活執行緒數:{active_count()}')  # 打印出當前有多少存活的執行緒

# 子執行緒開始
# I'm Thread1
# 子執行緒開始
# I'm Thread2
# 存活執行緒數:3
# 子執行緒結束
# 子執行緒結束
from threading import Thread, current_thread, active_count
import time
import os


def task(t_name, n):
    print(f'{t_name}開始')
    print(f'當前執行緒:{current_thread().name}')  # 執行緒名字
    print(f'當前執行緒所在的程序ID:{os.getpid()}')
    time.sleep(n)
    print(f'{t_name}結束')


if __name__ == '__main__':
    t1 = Thread(target=task, name='Thread1', args=('t1', 2))
    t2 = Thread(target=task, name='Thread2', args=('t2', 5))
    t1.start()
    t2.start()
    t1.join()
    print(f'執行緒t1是否存活:{t1.is_alive()}')
    print(f'執行緒t2是否存活:{t2.is_alive()}')

    # 當作執行緒id號
    print(f'執行緒t1的ID:{t1.ident}')
    print(f'執行緒t2的ID:{t2.ident}')

    print(f'當前執行緒所在的程序ID:{os.getpid()}')
    print(f'存活執行緒數:{active_count()}')  # 打印出2 ,存活的是t2和主執行緒


# t1開始
# 當前執行緒:Thread1
# 當前執行緒所在的程序ID:37420
# t2開始
# 當前執行緒:Thread2
# 當前執行緒所在的程序ID:37420
# t1結束
# 執行緒t1是否存活:False
# 執行緒t2是否存活:True
# 執行緒t1的ID:45360
# 執行緒t2的ID:73396
# 當前執行緒所在的程序ID:37420
# 存活執行緒數:2
# t2結束

知識點:

1.獲取執行緒名:t.namet.getName()

2.檢視當前程序下有幾個執行緒存活:active_count()

3.檢視當前執行緒是否存活:t1.is_alive()

4.檢視執行緒ID:t1.ident

5.檢視當前執行緒所在的程序ID:os.getpid()

七:守護執行緒

from threading import Thread, current_thread, active_count
import time


def task(t_name, n):
    print(f'{t_name}開始')
    time.sleep(n)
    print(f'{t_name}結束')


if __name__ == '__main__':
    t1 = Thread(target=task, name='Thread1', args=('Thread1', 10))
    t2 = Thread(target=task, name='Thread2', args=('Thread2', 4))

    t1.setDaemon(True)

    t1.start()
    t2.start()

    print('主執行緒')


# Thread1開始
# Thread2開始
# 主執行緒
# Thread2結束

八:執行緒互斥鎖

from threading import Thread, Lock
import time

money = 99


def task(n, mutex):
    global money
    mutex.acquire()     # 在修改資料的時候,加鎖
    temp = money
    # time.sleep(random.randint(1, 2))
    time.sleep(0.1)
    money = temp - 1
    mutex.release()     # 修改完之後,釋放鎖,其他執行緒就可以搶到鎖


if __name__ == '__main__':
    ll = []
    mutex = Lock()
    for i in range(10):
        t = Thread(target=task, args=(i, mutex))
        t.start()
        # t.join()    # 不能在這裡join,會變成序列
        ll.append(t)

    for i in ll:
        i.join()

    print(money)


# 89

九:GIL 全域性直譯器鎖

1.什麼是GIL?

官網解釋:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

在CPython中,全域性直譯器鎖(GIL)是一個互斥體,它阻止多個本機執行緒同時執行Python位元組碼。cpyth的執行緒管理不是必需的。(然而,由於GIL的存在,其他特性也越來越依賴於它所執行的保證。)

GILGlobal Interpreter Lock)全域性直譯器鎖

首先需要明確的一點GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念

同樣一段程式碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行,然而因為CPython是大部分環境下預設的Python執行環境

所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷,所有GIL並不是python的特性,僅僅是因為歷史原因在Cpython直譯器中難以移除

2.GIL存在的原因

CPython在執行多執行緒的時候並不是執行緒安全的,所以為了程式的穩定性,加一把全域性解釋鎖,能夠確保任何時候都只有一個Python執行緒執行。

②因為垃圾回收機制的存在,如果一個定義了一個x=2,如果在一種較為極端的情況下,x已經生成,但是2還沒賦值給x,此時的x就會被垃圾回收機制當成垃圾 被回收,為了避免這種情況出現,加了一把全域性直譯器鎖。

3.執行緒釋放GIL鎖的2種情況

①.遇到I/O操作

例如:發出了HTTP請求,需要等待響應讀寫檔案...

如果是

Time Tick 到期

Time Tick規定了執行緒的最長執行時間超過時間自動釋放GIL鎖

3.單核與多核情況下的多執行緒任務排程

單核:

由此可以發現:由於GIL機制的存在,單核CPU在同一時刻,只有一個執行緒在執行。

當執行緒遇到了I/O操作或者Time Tick到期的時候,就會釋放GIL鎖release GIL

此時,其他2個執行緒去搶這把鎖,搶到之後,執行緒才能執行。

雖然出現I/O操作Time Tick到期都會釋放GIL鎖,但是這2種情況是不一樣的:

出現I/O:

如果Thread1出現了IO操作,釋放了GIL鎖,Thread1會主動放棄搶鎖,不再參與這場競爭。

Time Tick到期:

如果Thread1出現了TimeTick到期,釋放了GIL鎖,Thread1會和其他執行緒競爭,同時搶這把GIL鎖

  • 所以,在單核的情況下,CPU的利用率是很高的。

多核:

由此可以發現:Thread1在CPU1上執行,Thread2在CPU2上執行。

GIL是全域性的,CPU2上的Thread2需要等待CPU1上的Thread1讓出GIL鎖,才有可能執行。

如果在多次競爭中,Thread2都勝出,Thread1沒有得到GIL鎖,意味著CPU1一直是閒置的,無法發揮多核的優勢。

所以,有了GIL的存在,CPython的的多執行緒 其實就是 單執行緒。

4.針對與cpython直譯器,有哪些注意點?

①針對IO密集型:開多執行緒

②針對計算密集型:開多程序

5.既然GIL有缺陷,為什麼還有這麼多人用呢?

①.python的庫多,庫都是基於cpython寫起來的,其他直譯器沒有那麼多的第三方庫(海納百川,有容乃大)。

②.雖然有這個歷史詬病,但是絲毫不影響我們敲程式碼(金無足赤,人無完人)。

③.程式碼是一成不變的,但是我們可以利用它的優勢,優化它的劣勢(取其精華,去其糟粕

④.Python牛逼!(人生苦短,我用Python

6.總結

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