日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

Python中的GIL和深浅拷贝

發(fā)布時間:2024/4/11 python 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python中的GIL和深浅拷贝 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Python中的GIL和深淺拷貝

文章目錄

  • Python中的GIL和深淺拷貝
    • 一、GIL全局解釋器鎖
      • 1.引入
      • 2、GIL
      • 3、Python GIL底層實現(xiàn)原理
      • 4、計算密集型和IO密集型
      • 5、解決GIL問題的方案:
      • 6、線程釋放GIL鎖的情況:
      • 7、GIL對python多線程的影響?闡明多線程程序是否可比單線程性能有提升,并解釋原因。
    • 二、深淺拷貝
      • 1、 淺拷貝
      • 2. 深拷貝
      • 3、拷貝的其他方式
      • 4. 注意點
      • 5、copy.copy和copy.deepcopy的區(qū)別
      • 6、總結(jié)

一、GIL全局解釋器鎖

1.引入

GIL,中文譯為全局解釋器鎖。在講解 GIL 之前,首先通過一個例子來直觀感受一下 GIL 在 Python 多線程程序運行的影響。

首先運行如下程序:

import time start = time.clock() def CountDown(n):while n > 0:n -= 1 CountDown(100000) print("Time used:",(time.clock() - start)) 運行結(jié)果為: Time used: 0.0039529000000000005

在我們的印象中,使用多個(適量)線程是可以加快程序運行效率的,因此可以嘗試將上面程序改成如下方式:

import time from threading import Thread start = time.clock() def CountDown(n):while n > 0:n -= 1 t1 = Thread(target=CountDown, args=[100000 // 2]) t2 = Thread(target=CountDown, args=[100000 // 2]) t1.start() t2.start() t1.join() t2.join() print("Time used:",(time.clock() - start))運行結(jié)果為: Time used: 0.006673
  • 可以看到,此程序中使用了 2 個線程來執(zhí)行和上面代碼相同的工作,但從輸出結(jié)果中可以看到,運行效率非但沒有提高,反而降低了。
  • 如果使用更多線程進行嘗試,會發(fā)現(xiàn)其運行效率和 2 個線程效率幾乎一樣(本機器測試使用 4 個線程,其執(zhí)行效率約為 0.005)
  • 是不是和你猜想的結(jié)果不一樣?事實上,得到這樣的結(jié)果是肯定的,因為 GIL 限制了 Python 多線程的性能不會像我們預(yù)期的那樣。

2、GIL

GIL 是最流程的 CPython 解釋器(平常稱為 Python)中的一個技術(shù)術(shù)語,中文譯為全局解釋器鎖,其本質(zhì)上類似操作系統(tǒng)的 Mutex。GIL 的功能是:在 CPython 解釋器中執(zhí)行的每一個 Python 線程,都會先鎖住自己,以阻止別的線程執(zhí)行。

當(dāng)然,CPython 不可能容忍一個線程一直獨占解釋器,它會輪流執(zhí)行 Python 線程。這樣一來,用戶看到的就是“偽”并行,即 Python 線程在交替執(zhí)行,來模擬真正并行的線程。

有讀者可能會問,既然 CPython 能控制線程偽并行,為什么還需要 GIL 呢?其實,這和 CPython 的底層內(nèi)存管理有關(guān)。

CPython 使用引用計數(shù)來管理內(nèi)容,所有 Python 腳本中創(chuàng)建的實例,都會配備一個引用計數(shù),來記錄有多少個指針來指向它。當(dāng)實例的引用計數(shù)的值為 0 時,會自動釋放其所占的內(nèi)存。

舉個例子,看如下代碼:

>>> import sys >>> a = [] >>> b = a >>> sys.getrefcount(a) 3
  • 可以看到,a 的引用計數(shù)值為 3,因為有 a、b 和作為參數(shù)傳遞的 getrefcount 都引用了一個空列表。
  • 假設(shè)有兩個 Python 線程同時引用 a,那么雙方就都會嘗試操作該數(shù)據(jù),很有可能造成引用計數(shù)的條件競爭,導(dǎo)致引用計數(shù)只增加1(實際應(yīng)增加 2)
  • 這造成的后果是,當(dāng)?shù)谝粋€線程結(jié)束時,會把引用計數(shù)減少 1,此時可能已經(jīng)達到釋放內(nèi)存的條件(引用計數(shù)為 0),當(dāng)?shù)?2 個線程再次視圖訪問 a 時,就無法找到有效的內(nèi)存了。
  • 所以,CPython 引進 GIL,可以最大程度上規(guī)避類似內(nèi)存管理這樣復(fù)雜的競爭風(fēng)險問題。

3、Python GIL底層實現(xiàn)原理

  • 上面這張圖,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3
    輪流執(zhí)行
  • 每一個線程在開始執(zhí)行時,都會鎖住 GIL,以阻止別的線程執(zhí)行;
  • 同樣的,每一個線程執(zhí)行完一段后,會釋放GIL,以允許別的線程開始利用資源。
  • 讀者可能會問,為什么 Python 線程會去主動釋放 GIL 呢?畢竟,如果僅僅要求 Python 線程在開始執(zhí)行時鎖住 GIL,且永遠不去釋放 GIL,那別的線程就都沒有運行的機會。
  • 其實,CPython 中還有另一個機制,叫做間隔式檢查(check_interval),意思是 CPython 解釋器會去輪詢檢查線程 GIL 的鎖住情況,每隔一段時間,Python 解釋器就會強制當(dāng)前線程去釋放 GIL,這樣別的線程才能有執(zhí)行的機會。
  • 注意,不同版本的 Python,其間隔式檢查的實現(xiàn)方式并不一樣。早期的 Python 是 100 個刻度(大致對應(yīng)了 1000 個字節(jié)碼);
  • 而 Python 3 以后,間隔時間大致為 15 毫秒。當(dāng)然,我們不必細究具體多久會強制釋放 GIL,讀者只需要明白,CPython 解釋器會在一個“合理”的時間范圍內(nèi)釋放 GIL 就可以了。

整體來說,每一個 Python 線程都是類似這樣循環(huán)的封裝,來看下面這段代碼:

for (;;) {if (--ticker < 0) {ticker = check_interval; /* Give another thread a chance */PyThread_release_lock(interpreter_lock);/* Other threads may run now */ PyThread_acquire_lock(interpreter_lock, 1);}bytecode = *next_instr++;switch (bytecode) {/* execute the next instruction ... */} }
  • 從這段代碼中可以看出,每個 Python 線程都會先檢查 ticker 計數(shù)。只有在 ticker 大于 0的情況下,線程才會去執(zhí)行自己的代碼。
  • Python GIL不能絕對保證線程安全
  • 注意,有了 GIL,并不意味著 Python 程序員就不用去考慮線程安全了,因為即便 GIL 僅允許一個 Python 線程執(zhí)行,但別忘了 Python 還有 check interval 這樣的搶占機制。

比如,運行如下代碼:

import threading n = 0 def foo():global nn += 1 threads = [] for i in range(100):t = threading.Thread(target=foo)threads.append(t) for t in threads:t.start() for t in threads:t.join() print(n)

執(zhí)行此代碼會發(fā)現(xiàn),其大部分時候會打印 100,但有時也會打印 99 或者 98,原因在于 n+=1 這一句代碼讓線程并不安全。如果去翻譯 foo 這個函數(shù)的字節(jié)碼就會發(fā)現(xiàn),它實際上是由下面四行字節(jié)碼組成:

>>> import dis >>> dis.dis(foo) LOAD_GLOBAL 0 (n) LOAD_CONST 1 (1) INPLACE_ADD STORE_GLOBAL 0 (n)

而這四行字節(jié)碼中間都是有可能被打斷的!所以,千萬別以為有了 GIL 程序就不會產(chǎn)生線程問題,我們?nèi)匀恍枰⒁饩€程安全。

4、計算密集型和IO密集型

遇到IO阻塞時,正在執(zhí)行的線程會暫時釋放GIL鎖,這時其它線程會利用這個空隙時間,執(zhí)行自己的代碼,因此多線程抓取比單線程抓取性能要好。

  • 計算密集型:要進行大量的數(shù)值計算,例如進行上億的數(shù)字計算、計算圓周率、對視頻進行高清解碼等等。這種計算密集型任務(wù)雖然也可以用多任務(wù)完成,但是花費的主要時間在任務(wù)切換的時間,此時CPU執(zhí)行任務(wù)的效率比較低。
  • IO密集型:涉及到網(wǎng)絡(luò)請求(time.sleep())、磁盤IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點是CPU消耗很少,任務(wù)的大部分時間都在等待IO操作完成(因為IO的速度遠遠低于CPU和內(nèi)存的速度)。對于IO密集型任務(wù),任務(wù)越多,CPU效率越高,但也有一個限度。

5、解決GIL問題的方案:

1.使用其它語言,例如C,Java

2.使用其它解釋器,如java的解釋器jython

3.使用多進程

6、線程釋放GIL鎖的情況:

1.在IO操作等可能會引起阻塞的system call之前,可以暫時釋放GIL,但在執(zhí)行完畢后,必須重新獲取GIL。

2.Python 3.x使用計時器(執(zhí)行時間達到閾值后,當(dāng)前線程釋放GIL)或Python 2.x,tickets計數(shù)達到100。

7、GIL對python多線程的影響?闡明多線程程序是否可比單線程性能有提升,并解釋原因。

  • Python語言和GIL沒有半毛錢關(guān)系。僅僅是由于歷史原因在Cpython虛擬機(解釋器),難以移除GIL。
  • GIL:全局解釋器鎖。每個線程在執(zhí)行的過程都需要先獲取GIL,保證同一時刻只有一個線程可以執(zhí)行代碼。
  • 線程釋放GIL鎖的情況: 在IO操作等可能會引起阻塞的system call之前,可以暫時釋放GIL,但在執(zhí)行完畢后,必須重新獲取GIL Python 3.x使用計時器(執(zhí)行時間達到閾值后,當(dāng)前線程釋放GIL)或Python 2.x,tickets計數(shù)達到100
  • Python使用多進程是可以利用多核的CPU資源的。
  • 多線程爬取比單線程性能有提升,因為遇到IO阻塞會自動釋放GIL鎖
  • 二、深淺拷貝

    1、 淺拷貝

    淺拷貝是對于一個對象的頂層拷貝
    通俗的理解是:拷貝了引用,并沒有拷貝內(nèi)容


    2. 深拷貝

    深拷貝是對于一個對象所有層次的拷貝(遞歸)


    3、拷貝的其他方式

    • 分片表達式可以賦值一個序列

    • 字典的copy方法可以拷貝一個字典

    4. 注意點

    淺拷貝對不可變類型和可變類型的copy不同
    copy.copy對于可變類型,會進行淺拷貝
    copy.copy對于不可變類型,不會拷貝,僅僅是指向

    In [88]: a = [11,22,33] In [89]: b = copy.copy(a) In [90]: id(a) Out[90]: 59275144 In [91]: id(b) Out[91]: 59525600 In [92]: a.append(44) In [93]: a Out[93]: [11, 22, 33, 44] In [94]: b Out[94]: [11, 22, 33]In [95]: a = (11,22,33) In [96]: b = copy.copy(a) In [97]: id(a) Out[97]: 58890680 In [98]: id(b) Out[98]: 58890680

    5、copy.copy和copy.deepcopy的區(qū)別

    • copy.copy

    • copy.deepcopy


    6、總結(jié)

    • 深淺拷貝都是對源對象的復(fù)制,占用不同的內(nèi)存空間
    • 如果源對象只有一級目錄的話,源做任何改動,不影響深淺拷貝對象
    • 如果源對象不止一級目錄的話,源做任何改動,都要影響淺拷貝,但不影響深拷貝
    • 序列對象的切片其實是淺拷貝,即只拷貝頂級的對象

    總結(jié)

    以上是生活随笔為你收集整理的Python中的GIL和深浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。