python下劃線的作用
單下劃線 _
作爲一個臨時或者無關緊要的變量
a, _, _, b = (1, 2, 3, 4)
print(a, b) # 1 4
前導單下劃線 _test
命名約定,告知其他程序員該變量或者方法僅供內部使用
不會對python解釋器的行爲產生影響
前導雙下劃線 __test
前導雙下劃線會讓python解釋器重寫屬性名稱,可以避免子類中的命名衝突
class A:
def runs(self):
pass
class B(A):
def __runs(self):
pass
a = A()
b = B()
print(dir(a))
print(dir(b))
out:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'runs']
['_B__runs', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'runs']
末尾單下劃線 test_
當變量名與關鍵字衝突時,加一個下劃線解決衝突
雙前導雙末尾下劃線 __test__
被雙前導雙末尾下劃線修飾的名稱被用作特殊用途,如__init__
等,這些方法被稱爲神奇方法或者魔術方法。
在自己的程序中避免使用這種方法來命名自己的變量,python解釋器的後續更新可能會使用該變量。
三次握手及四次揮手
三次握手
- 客戶端發送SYN報文交給服務器端,進入SYN_SENT狀態。
- 服務器端收到SYN報文,迴應一個SYN+ACK報文,進入SYN_RECV狀態。
- 客戶端收到服務器端的SYN報文,迴應一個ACK報文,客戶端和服務器進入ESTABLISHED狀態。
四次揮手
- 某個應用進程首先調用close,稱該端執行主動關閉。該端的TCP發送一個FIN表示數據發送完畢。
- 接收到這個FIN的對端執行被動關閉,迴應一個ACK表示確認。
- 一段時間後,對端發送完所有數據,調用close,發送一個FIN
- 該端接收到FIN,迴應一個ACK。
線程進程的關係
- 同一進程中的線程共享同一內存空間,但是進程之間是獨立的。
- 同一個進程中的所有線程的數據是共享的,進程之間的數據是獨立的。
- 對主線程的修改可能影響其他線程的行爲,但是父進程的修改(除了刪除)不會影響其他子進程。
- 線程是一個上下文的執行指令,而進程則是與運算相關的一簇資源。
- 同一個進程的線程之間可以直接通信,但是進程之間的交流需要藉助中間代理來實現。
- 創建新的線程很容易,但是創建新的進程需要對父進程做一次複製。
- 一個線程可以操作同一進程的其他線程,但是進程只能操作其子進程。
- 線程啓動速度快,進程啓動速度慢(但是兩者運行速度沒有可比性)。
GIL
GIL(Global Interpreter Lock):全局解釋器鎖
每個線程在執行的過程中都需要先獲取GIL,保證同一時刻只有一個線程可以執行代碼。
GIL並不是python的特性,GIL屬於cpython。
隨着多核CPU的出現,爲了有效利用多核CPU的性能,就出現了多線程的編程方式,而隨之以來的是線程間數據一致性和狀態同步的困難。
線程是非獨立的,所以同一進程裏線程是數據共享的,當各個線程訪問數據資源時會出現競爭狀態,即數據可能同時被多個線程佔用,造成數據混亂,這就是線程的不安全。
demo:
import threading
a = 0
def thread_run():
global a
for i in range(1000000):
a += 1
print(a)
def main():
t1 = threading.Thread(target=thread_run)
t2 = threading.Thread(target=thread_run)
t1.start()
t2.start()
if __name__ == '__main__':
main()
# 1357088
# 1511505
可以發現結果並不是預期的2000000,這是由於資源競爭產生的問題。
雖然有了GIL但還是出現了上述資源競爭的問題,這是由於GIL保護的是解釋器級別的數據,如垃圾回收的數據,用戶開發的程序的數據不受GIL保護,上述代碼僅用於說明資源競爭問題
原子操作就是不會因爲進程併發或者線程併發而導致被中斷的操作。原子操作的特點就是要麼一次全部執行,要麼全不執行。不存在執行了一半而被中斷的情況。
當對全局資源存在寫操作時,如果不能保證寫入過程的原子性,會出現髒讀髒寫的情況。
上述代碼中的a+=1並不是原子的,它實際經過了三步:
- 讀入a變量指向的值
- +1
- 讓a變量指向新的結果值
如果在這三步之間被解釋器打斷奪走GIL就會導致更新丟失,爲了解決這個問題需要再人工加鎖。
與之相似,爲了解決多線程之間數據完整性和狀態同步的問題,GIL誕生了。
GIL保證了任意時刻只有一個線程再解釋器中運行。即Python中的多線程是表面上的多線程,是併發執行而並不是並行執行。