程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

關於python3的一些理解(裝飾器、垃圾回收、進程線程協程、全局解釋器鎖等)

編輯:Python

文章目錄

    • 一、什麼是*args 和 **kwargs?
      • 1.1 為什麼會有 *args 和 **kwargs?
      • 1.2 *args 和 **kwargs 的用途是什麼?
      • 1.3 *args 是什麼?
      • 1.4 **kwargs是什麼?
      • 1.5 *args 與 **kwargs 的區別是什麼?
    • 二、什麼是裝飾器?
      • 2.1 裝飾器是什麼?
      • 2.2 裝飾器怎麼用?
    • 三、Python垃圾回收(GC)
      • 3.1 垃圾回收算法有哪些?
      • 3.2 引用計數(主要)是什麼?
      • 3.3 標記-清除是什麼?
      • 3.4 分代回收是什麼?
    • 四、python的sorted函數對字典按key排序和按value排序
      • 4.1 python 的sorted函數是什麼?
      • 4.2 python 的sorted函數舉例說明?
    • 五、直接賦值、淺拷貝和深度拷貝
      • 5.1 概念介紹
      • 5.2 介紹
      • 5.3 變量定義流程
      • 5.3 賦值
      • 5.4 淺拷貝
      • 5.5 深度拷貝
      • 5.6 核心:不可變對象類型 and 可變對象類型
        • 5.6.1 不可變對象類型
        • 5.6.2 可變對象類型
    • 六、進程、線程、協程
      • 6.1 進程
        • 6.1.1 什麼是進程?
        • 6.1.2 進程間如何通信?
      • 6.2 線程
        • 6.2.1 什麼是線程?
        • 6.2.2 線程間如何通信?
      • 6.3 進程 vs 線程
        • 6.3.1 區別
        • 6.3.2 應用場景
      • 6.4 協程
        • 6.4.1 什麼是協程?
        • 6.4.2 協程的優點?
    • 七、全局解釋器鎖
      • 7.1 什麼是全局解釋器鎖?
      • 7.2 GIL有什麼作用?
      • 7.3 GIL有什麼影響?
      • 7.4 如何避免GIL帶來的影響?

一、什麼是*args 和 **kwargs?

1.1 為什麼會有 *args 和 **kwargs?

對於一些編寫的函數,可能預先並不知道, 函數使用者會傳遞多少個參數給你, 所以在這個場景下使用這兩個關鍵字。

1.2 *args 和 **kwargs 的用途是什麼?

*args 和 **kwargs 主要用於函數定義。 你可以將不定數量的參數傳遞給一個函數;

1.3 *args 是什麼?

  • 介紹:用來發送一個非鍵值對的可變數量的參數列表給一個函數;
  • 舉例
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)
test_var_args('yasoob', 'python', 'eggs', 'test')
# output
first normal arg: yasoob
another arg through *argv: python
another arg through *argv: eggs
another arg through *argv: test

1.4 **kwargs是什麼?

  • 介紹:允許將不定長度的鍵值對, 作為參數傳遞給一個函數。 如果你想要在一個函數裡處理帶名字的參數, 你應該使用kwargs;
  • 舉例:
def greet_me(**kwargs):
for key, value in kwargs.items():
print("{0} == {1}".format(key, value))
# output
>>> greet_me(name="yasoob")
name == yasoob

1.5 *args 與 **kwargs 的區別是什麼?

def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)

使用 *args

>>> args = ("two", 3, 5)
>>> test_args_kwargs(*args)
arg1: two
arg2: 3
arg3: 5

使用 **kwargs

>>> kwargs = {
"arg3": 3, "arg2": "two", "arg1": 5}
>>> test_args_kwargs(**kwargs)
arg1: 5
arg2: two
arg3: 3

二、什麼是裝飾器?

2.1 裝飾器是什麼?

  • 裝飾器本質:一個 Python 函數或類;
  • 作用:可以讓其他函數或類在不需要做任何代碼修改的前提下增加額外功能,裝飾器的返回值也是一個函數/類對象。
  • 使用場景:經常用於有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。
  • 優點:有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼到裝飾器中並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。

2.2 裝飾器怎麼用?

簡單裝飾器

def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 當做參數傳遞進來時,執行func()就相當於執行foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因為裝飾器 use_logging(foo) 返回的時函數對象 wrapper,這條語句相當於 foo = wrapper
foo() # 執行foo()就相當於執行 wrapper()

@ 語法糖

def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()

帶參數的裝飾器

# 功能:加載數據
def loadData(filename):
''' 功能:加載數據 input: filename String 文件名稱 return: data List 數據列表 '''
data = []
with open(filename,"r",encoding="utf-8") as f:
line = f.readline().replace("\n","")
while line:
data.append(line)
line = f.readline().replace("\n","")
return data
# 功能:裝飾器 之 數據采樣
def simpleData(func):
''' 功能:裝飾器 之 數據采樣 '''
def wrapper(*args):
dataList = func(*args)
rate = 0.05
dataListLen = len(dataList)
if dataListLen>100000:
rate = 0.001
elif dataListLen>10000:
rate = 0.01
elif dataListLen>1000:
rate = 0.05
elif dataListLen>100:
rate = 0.1
else:
rate =1
shuffle(dataList)
simpleDataList =dataList[:int(rate*len(dataList))]
return dataList,simpleDataList
return wrapper
# 使用
dataList,simpleDataList = simpleData(loadData)(f"{
basePath}{
name}.txt")

三、Python垃圾回收(GC)

3.1 垃圾回收算法有哪些?

  • 引用計數
  • 標記-清除
  • 分代回收

3.2 引用計數(主要)是什麼?

  • 核心:每一個對象的核心就是一個結構體PyObject,它的內部有一個引用計數器(ob_refcnt)
 typedef struct_object {

int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
  • 介紹:在Python中每一個對象的核心就是一個結構體PyObject,它的內部有一個引用計數器(ob_refcnt)。程序在運行的過程中會實時的更新ob_refcnt的值,來反映引用當前對象的名稱數量。當某對象的引用計數值為0,那麼它的內存就會被立即釋放掉。

  • 以下情況是導致引用計數加一的情況:

    • 對象被創建,例如a=2
    • 對象被引用,b=a
    • 對象被作為參數,傳入到一個函數中
    • 對象作為一個元素,存儲在容器中
  • 下面的情況則會導致引用計數減一:

    • 對象別名被顯示銷毀 del
    • 對象別名被賦予新的對象
    • 一個對象離開他的作用域
    • 對象所在的容器被銷毀或者是從容器中刪除對象
  • 優點:如高效、實現邏輯簡單、具備實時性,一旦一個對象的引用計數歸零,內存就直接釋放了。不用像其他機制等到特定時機。將垃圾回收隨機分配到運行的階段,處理回收內存的時間分攤到了平時,正常程序的運行比較平穩。

  • 缺點:

    • 邏輯簡單,但實現有些麻煩。每個對象需要分配單獨的空間來統計引用計數,這無形中加大的空間的負擔,並且需要對引用計數進行維護,在維護的時候很容易會出錯。
    • 在一些場景下,可能會比較慢。正常來說垃圾回收會比較平穩運行,但是當需要釋放一個大的對象時,比如字典,需要對引用的所有對象循環嵌套調用,從而可能會花費比較長的時間。
    • 循環引用。這將是引用計數的致命傷,引用計數對此是無解的,因此必須要使用其它的垃圾回收算法對其進行補充。
 a=[1,2]
b=[2,3]
a.append(b)
b.append(a)
DEL a
DEL b

說實話感覺還有點像死鎖的問題,這種問題出現在可以循環的結構中List Dict Object等等,如上代碼a、b間的引用都為1,而a、b被引用的對象刪除後都各自減去1(所以他們各自的引用計數還是1),這時候就尴尬了啊,都是1就有了免死金牌(一直是1不會變化了)。這樣的情況單單靠引用計數就無法解決了。

3.3 標記-清除是什麼?

  • 動機:用來解決循環引用的問題的只有容器對象才會出現引用循環,比如列表、字典、類、元組。
  • 思路:
    • A)標記階段,遍歷所有的對象,如果是可達的(reachable),也就是還有對象引用它,那麼就標記該對象為可達;
    • B)清除階段,再次遍歷對象,如果發現某個對象沒有標記為可達,則就將其回收。
  • 指針:
    • root鏈表(root object)
    • unreachable鏈表

情況一

 a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a
del b

對於情景A,原來再未執行DEL語句的時候,a,b的引用計數都為2(init+append=2),但是在DEL執行完以後,a,b引用次數互相減1。a,b陷入循環引用的圈子中,然後標記-清除算法開始出來做事,找到其中一端a,開始拆這個a,b的引用環(我們從A出發,因為它有一個對B的引用,則將B的引用計數減1;然後順著引用達到B,因為B有一個對A的引用,同樣將A的引用減1,這樣,就完成了循環引用對象間環摘除。),去掉以後發現,a,b循環引用變為了0,所以a,b就被處理到unreachable鏈表中直接被做掉。

情況二

 a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a

對於情景B,簡單一看那b取環後引用計數還為1,但是a取環,就為0了。這個時候a已經進入unreachable鏈表中,已經被判為死刑了,但是這個時候,root鏈表中有b。如果a被做掉,那世界上還有什麼正義… ,在root鏈表中的b會被進行引用檢測引用了a,如果a被做掉了,那麼b就…涼涼,一審完事,二審a無罪,所以被拉到了root鏈表中。

3.4 分代回收是什麼?

  • 垃圾回收=垃圾檢測+釋放。
  • 動機:對於程序,存在一定比例的內存塊的生存周期比較短;而剩下的內存塊,生存周期會比較長,甚至會從程序開始一直持續到程序結束。生存期較短對象的比例通常在 80%~90% 之間,這種思想簡單點說就是:對象存在時間越長,越可能不是垃圾,應該越少去收集。這樣在執行標記-清除算法時可以有效減小遍歷的對象數,從而提高垃圾回收的速度。
  • 三代(generation 0,1,2):
    • 0代表幼年對象,
    • 1代表青年對象,
    • 2代表老年對象
  • 根據弱代假說(越年輕的對象越容易死掉,老的對象通常會存活更久。)
  • 新生的對象被放入0代,如果該對象在第0代的一次gc垃圾回收中活了下來,那麼它就被放到第1代裡面(它就升級了)。如果第1代裡面的對象在第1代的一次gc垃圾回收中活了下來,它就被放到第2代裡面。

四、python的sorted函數對字典按key排序和按value排序

4.1 python 的sorted函數是什麼?

  • sorted 函數
    • 表達式:sorted(iterable,key,reverse)
    • 參數:
      • iterable表示可以迭代的對象,例如可以是dict.items()、dict.keys()等;
      • key是一個函數,用來選取參與比較的元素;
      • reverse則是用來指定排序是倒序還是順序,reverse=true則是倒序,reverse=false時則是順序,默認時reverse=false。

4.2 python 的sorted函數舉例說明?

  • 按key值對字典排序

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-b9lCbmq5-1653633156450)(img/微信截圖_20201104224302.png)]

直接使用sorted(d.keys())就能按key值對字典排序,這裡是按照順序對key值排序的,如果想按照倒序排序的話,則只要將reverse置為true即可。

  • sorted函數按value值對字典排序

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QhICVrFW-1653633156451)(img/微信截圖_20201104224402.png)]

d.items()實際上是將d轉換為可迭代對象,迭代對象的元素為(‘lilee’,25)、(‘wangyan’,21)、(‘liqun’,32)、(‘lidaming’,19),items()方法將字典的元素轉化為了元組,而這裡key參數對應的lambda表達式的意思則是選取元組中的第二個元素作為比較參數(如果寫作key=lambda item:item[0]的話則是選取第一個元素作為比較對象,也就是key值作為比較對象。lambda x:y中x表示輸出參數,y表示lambda函數的返回值),所以采用這種方法可以對字典的value進行排序。注意排序後的返回值是一個list,而原字典中的名值對被轉換為了list中的元組。

五、直接賦值、淺拷貝和深度拷貝

5.1 概念介紹

  • 變量:是一個系統表的元素,擁有指向對象的連接空間
  • 對象:被分配的一塊內存,存儲其所代表的值
  • 引用:是自動形成的從變量到對象的指針
  • 類型:屬於對象,而非變量
  • 不可變對象:一旦創建就不可修改的對象,包括字符串、元組、數值類型(該對象所指向的內存中的值不能被改變。當改變某個變量時候,由於其所指的值不能被改變,相當於把原來的值復制一份後再改變,這會開辟一個新的地址,變量再指向這個新的地址。)
  • 可變對象:可以修改的對象,包括列表、字典、集合(該對象所指向的內存中的值可以被改變。變量(准確的說是引用)改變後,實際上是其所指的值直接發生改變,並沒有發生復制行為,也沒有開辟新的地址,通俗點說就是原地改變。)

5.2 介紹

  • 直接賦值:其實就是對象的引用(別名)
  • 淺拷貝(copy):拷貝父對象不會拷貝對象的內部的子對象
  • 深拷貝(deepcopy): copy 模塊的 deepcopy 方法,完全拷貝了父對象及其子對象

5.3 變量定義流程

對於 : a = {1: [1,2,3]}

python解釋器流程:

  1. 創建變量a;
  2. 創建一個對象(分配一塊內存),來存儲值 {1: [1,2,3]};
  3. 將變量與對象,通過指針連接起來,從變量到對象的連接稱之為引用(變量引用對象)

5.3 賦值

  • 介紹:b = a。賦值引用,只復制新對象的引用,不會開辟新內存空間,此時 a 和 b 都指向同一個對象;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LNnA6MAb-1653633156452)(img/微信截圖_20210221224625.png)]

5.4 淺拷貝

  • 介紹:a 和 b 是一個獨立的對象,但他們的子對象還是指向統一對象(是引用)。
  • 三種形式: eg: lst = [1,2,[3,4]]
    • 切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]
    • 工廠函數:lst1 = list(lst)
    • copy函數:lst1 = copy.copy(lst)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gI46eUXy-1653633156452)(img/微信截圖_20210221224952.png)]

  • 淺復制要分兩種情況進行討論:
    • 1)當淺復制的值是不可變對象(字符串、元組、數值類型)時和“賦值”的情況一樣,對象的id值(id()函數用於獲取對象的內存地址)與淺復制原來的值相同。
    • 2)當淺復制的值是可變對象(列表、字典、集合)時會產生一個“不是那麼獨立的對象”存在。有兩種情況:
      • 第一種情況:復制的對象中無復雜子對象,原來值的改變並不會影響淺復制的值,同時淺復制的值改變也並不會影響原來的值。原來值的id值與淺復制原來的值不同。
      • 第二種情況:復制的對象中有復雜子對象(例如列表中的一個子元素是一個列表),如果不改變其中復雜子對象,淺復制的值改變並不會影響原來的值。 但是改變原來的值中的復雜子對象的值會影響淺復制的值。

5.5 深度拷貝

  • 介紹:b = copy.deepcopy(a)。 a 和 b 完全拷貝了父對象及其子對象,兩者是完全獨立的。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zE2FOao4-1653633156453)(img/微信截圖_20210221225331.png)]

5.6 核心:不可變對象類型 and 可變對象類型

5.6.1 不可變對象類型

  • 介紹:不可變類型,不管是深拷貝還是淺拷貝,地址值和拷貝後的值都是一樣的
 import copy
a=(1,2,3)
print("=====賦值=====")
b=a
print(a)
print(b)
print(id(a))
print(id(b))
print("=====淺拷貝=====")
b=copy.copy(a)
print(a)
print(b)
print(id(a))
print(id(b))
print("=====深拷貝=====")
b=copy.deepcopy(a)
print(a)
print(b)
print(id(a))
print(id(b))
結果:
=====賦值=====
(1, 2, 3)
(1, 2, 3)
43481128
43481128
=====淺拷貝=====
(1, 2, 3)
(1, 2, 3)
43481128
43481128
=====深拷貝=====
(1, 2, 3)
(1, 2, 3)
43481128
43481128

5.6.2 可變對象類型

  • 介紹:
  1. 外層添加元素時,淺拷貝不會隨原列表變化而變化;內層添加元素時,淺拷貝才會變化。
  2. 無論原列表如何變化,深拷貝都保持不變。
  3. 賦值對象隨著原列表一起變化。

對於可變對象深淺拷貝(外層改變元素)

 import copy
l=[1,2,3,[4, 5]]
l1=l #賦值
l2=copy.copy(l) #淺拷貝
l3=copy.deepcopy(l) #深拷貝
l.append(6)
print(l)
print(l1)
print(l2)
print(l3)
結果:
[1, 2, 3, [4, 5], 6] #l添加一個元素6
[1, 2, 3, [4, 5], 6] #l1跟著添加一個元素6
[1, 2, 3, [4, 5]] #l2保持不變
[1, 2, 3, [4, 5]] #l3保持不變

對於可變對象深淺拷貝(內層改變元素)

 import copy
l=[1,2,3,[4, 5]]
l1=l #賦值
l2=copy.copy(l) #淺拷貝
l3=copy.deepcopy(l) #深拷貝
l[3].append(6)
print(l)
print(l1)
print(l2)
print(l3)
結果:
[1, 2, 3, [4, 5, 6]] #l[3]添加一個元素6
[1, 2, 3, [4, 5, 6]] #l1跟著添加一個元素6
[1, 2, 3, [4, 5, 6]] #l2跟著添加一個元素6
[1, 2, 3, [4, 5]] #l3保持不變

六、進程、線程、協程

6.1 進程

6.1.1 什麼是進程?

  • 介紹:是執行中的計算機程序。也就是說,每個代碼在執行的時候,首先本身即是一個進程。運行中每個進程都擁有自己的地址空間、內存、數據棧及其它資源。
  • 特點:
    • 多個進程可以在不同的 CPU 上運行,互不干擾
    • 同一個CPU上,可以運行多個進程,由操作系統來自動分配時間片
    • 由於進程間資源不能共享,需要進程間通信,來發送數據,接受消息等
    • 多進程,也稱為“並行”。

6.1.2 進程間如何通信?

  • 進程彼此之間互相隔離,要實現進程間通信(IPC),multiprocessing模塊支持兩種形式:隊列和管道,這兩種方式都是使用消息傳遞的。
  • 進程隊列queue:不同於線程queue,進程queue的生成是用multiprocessing模塊生成的。在生成子進程的時候,會將代碼拷貝到子進程中執行一遍,及子進程擁有和主進程內容一樣的不同的名稱空間。
  • 管道pipe:默認管道是全雙工的,如果創建管道的時候映射成False,左邊只能用於接收,右邊只能用於發送,類似於單行道;
  • 共享數據manage:Queue和pipe只是實現了數據交互,並沒實現數據共享,即一個進程去更改另一個進程的數據。
  • 進程池:開多進程是為了並發,通常有幾個cpu核心就開幾個進程,但是進程開多了會影響效率,主要體現在切換的開銷,所以引入進程池限制進程的數量。進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,如果進程池序列中沒有可供使用的進進程,那麼程序就會等待,直到進程池中有可用進程為止。

6.2 線程

6.2.1 什麼是線程?

  • 介紹:線程,是在進程中執行的代碼。一個進程下可以運行多個線程,這些線程之間共享主進程內申請的操作系統資源。在一個進程中啟動多個線程的時候,每個線程按照順序執行。現在的操作系統中,也支持線程搶占,也就是說其它等待運行的線程,可以通過優先級,信號等方式,將運行的線程掛起,自己先運行。

6.2.2 線程間如何通信?

  • 共享變量:創建全局變量,多個線程公用一個全局變量,方便簡單。但是壞處就是共享變量容易出現數據競爭,不是線程安全的,解決方法就是使用互斥鎖。
  • 變量共享引申出線程同步問題:如果多個線程共同對某個數據修改,則可能出現不可預料的結果,為了保證數據的正確性,需要對多個線程進行同步。 使用Thread對象的Lock和Rlock可以實現簡單的線程同步,這兩個對象都有acquire方法和release方法,對於那些需要每次只允許一個線程操作的數據,可以將其操作放到acquire和release方法之間。
  • 隊列:線程間使用隊列進行通信,因為隊列所有方法都是線程安全的,所以不會出現線程競爭資源的情況。Queue.Queue 是進程內非阻塞隊列

6.3 進程 vs 線程

6.3.1 區別

  • 一個進程中的各個線程與主進程共享相同的資源,與進程間互相獨立相比,線程之間信息共享和通信更加容易(都在進程中,並且共享內存等)。

  • 線程一般以並發執行,正是由於這種並發和數據共享機制,使多任務間的協作成為可能。

  • 進程一般以並行執行,這種並行能使得程序能同時在多個CPU上運行;

  • 區別:多個線程只能在進程申請到的的“時間片”內運行(一個CPU內的進程,啟動了多個線程,線程調度共享這個進程的可執行時間片),進程可以真正實現程序的“同時”運行(多個CPU同時運行)。

6.3.2 應用場景

  • 計算密集型任務使用多進程
  • IO密集型(如:網絡通訊)任務使用多線程,較少使用多進程. 【IO操作需要獨占資源】

6.4 協程

6.4.1 什麼是協程?

  • 介紹: 協程,又稱微線程,纖程,英文名Coroutine。協程的作用,是在執行函數A時,可以隨時中斷,去執行函數B,然後中斷繼續執行函數A(可以自由切換)。但這一過程並不是函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行.

6.4.2 協程的優點?

  • 協程的主要特色是:協程間是協同調度的,這使得並發量數萬以上的時候,協程的性能是遠遠高於線程。【注意這裡也是“並發”,不是“並行”。】

  • 協程優點:

    • 協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級
    • 單線程內就可以實現並發的效果,最大限度地利用cpu
  • 協程缺點:

    • 協程的本質是單線程下,無法利用多核,可以是一個程序開啟多個進程,每個進程內開啟多個線程,每個線程內開啟協程
    • 協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞整個線程
  • 使用場景:

    • 對於IO密集型任務非常適用,如果是cpu密集型,推薦多進程+協程的方式。

七、全局解釋器鎖

7.1 什麼是全局解釋器鎖?

GIL全稱Global Interpreter Lock,即全局解釋器鎖。 作用就是,限制多線程同時執行,保證同一時間內只有一個線程在執行。 GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。

7.2 GIL有什麼作用?

為了更有效的利用多核處理器的性能,就出現了多線程的編程方式,而隨之帶來的就是線程間數據的一致性和狀態同步的完整性。 python為了利用多核,開始支持多線程,但線程是非獨立的,所以同一進程裡線程是數據共享,當各個線程訪問數據資源時會出現競狀態,即數據可能會同時被多個線程占用,造成數據混亂,這就是線程的不安全。而解決多線程之間數據完整性和狀態同步最簡單的方式就是加鎖。GIL能限制多線程同時執行,保證同一時間內只有一個線程在執行。

7.3 GIL有什麼影響?

GIL無疑就是一把全局排他鎖。毫無疑問全局鎖的存在會對多線程的效率有不小影響。甚至就幾乎等於Python是個單線程的程序。

7.4 如何避免GIL帶來的影響?

  • 方法一:用進程+協程 代替 多線程的方式 在多進程中,由於每個進程都是獨立的存在,所以每個進程內的線程都擁有獨立的GIL鎖,互不影響。但是,由於進程之間是獨立的存在,所以進程間通信就需要通過隊列的方式來實現。

  • 方法二:更換解釋器。像JPython和IronPython這樣的解析器由於實現語言的特性,他們不需要GIL的幫助。然而由於用了Java/C#用於解析器實現,他們也失去了利用社區眾多C語言模塊有用特性的機會。所以這些解析器也因此一直都比較小眾。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved