在知乎上面看見一個(gè)很意思的問題,怎么樣才算是熟悉多線程編程?
https://www.zhihu.com/question/22375509 看了很多人的分享,整理一下大家的答案。
1.明白多進(jìn)程和多線程的基本概念。
多進(jìn)程之間的內(nèi)存空間是獨(dú)立的,所以多進(jìn)程之間多用的通信,而不是加鎖(因?yàn)樽兞康膬?nèi)存空間是獨(dú)立的,要加鎖也是“大鎖”,比為文件鎖這類的)。
進(jìn)程之間通信的機(jī)制?
socket,管道,消息隊(duì)列,共享內(nèi)存,信號(hào)(比如 在terminal 輸入 kill -9 就是發(fā)送kill信號(hào))線程之間的為什么要加鎖?如果不加鎖會(huì)有什么問題?進(jìn)程為什么不需要加鎖?
#多線程訪問共享變量不加鎖的情況
import threading
import time
counter =0
def process_item(): # 這段函數(shù)的功能很簡(jiǎn)單,就是給counter 執(zhí)行10w次的加1操作
global counter
for i in range(100000):
counter=counter + 1
# 創(chuàng)建兩個(gè)線程,分別執(zhí)行一次process_item
# 按照單線程的思想,如果執(zhí)行兩次process_item 之后,counter的計(jì)數(shù)應(yīng)該是20w
# but 在多線程的情況下面,會(huì)發(fā)現(xiàn)counter的值不是那么準(zhǔn)確。在大多數(shù)情況下它是對(duì)的,但有時(shí)它會(huì)比實(shí)際的少幾個(gè)。
t1 = threading.Thread(target=process_item,name=None)
t2 = threading.Thread(target=process_item,name=None)
t1.start()
t2.start()
t1.join()
t2.join()
print (counter) #輸出 190654,為什么不是20w ?
"""
原因也很簡(jiǎn)單,就是修改一個(gè)變量不是一個(gè)原子操作,實(shí)際上分成三步:
讀取--->修改--->寫回
考慮一下這種情況:在當(dāng)前線程獲取到counter值后,另一個(gè)線程搶占到了CPU,
然后同樣也獲取到了counter值,并進(jìn)一步將counter值重新計(jì)算并完成回寫;
之后時(shí)間片重新輪到當(dāng)前線程(這里僅作標(biāo)識(shí)區(qū)分,并非實(shí)際當(dāng)前),
此時(shí)當(dāng)前線程獲取到counter值還是原來的,
完成后續(xù)兩步操作后counter的值實(shí)際只加上1。
"""
加鎖之后的代碼:
import threading
import time
counter =0
def process_item():
global counter
for i in range(100000):
lock.acquire() # 加鎖
counter=counter + 1
lock.release() # 釋放鎖
lock = threading.Lock()
t1 = threading.Thread(target=process_item,name=None)
t2 = threading.Thread(target=process_item,name=None)
t1.start()
t2.start()
t1.join()
t2.join()
print (counter) #輸出 20w,結(jié)果是正常的
2.明白保護(hù)線程安全的基本方法有哪些
進(jìn)程之間一般討論的是通信,線程之間討論的是如何同步,線程互斥和同步的方式有:
互斥:
- 鎖(各種類型的加鎖) ;
同步:
- 信號(hào)量 Semaphores(可以理解是高級(jí)的鎖,因?yàn)閮?nèi)部的實(shí)現(xiàn)就是一個(gè)帶有計(jì)數(shù)的鎖);
- 事件 event
- 條件 Conditions
線程加鎖的類型
1.1 mutex 互斥鎖
1.2 遞歸鎖 / python 里面叫做 RLock
1.3 讀寫鎖
1.4 自旋鎖信號(hào)量 Semaphores
可以理解是高級(jí)的鎖,因?yàn)閮?nèi)部的實(shí)現(xiàn)就是一個(gè)帶有計(jì)數(shù)的鎖event
基于事件的同步是指:一個(gè)線程發(fā)送/傳遞事件,另外的線程等待事件的觸發(fā)。condition
條件同步機(jī)制是指:一個(gè)線程等待特定條件,而另一個(gè)線程發(fā)出特定條件滿足的信號(hào)。
信號(hào)量,事件,條件都是實(shí)現(xiàn)線程同步的機(jī)制,一些需要線程同步的問題都可以使用幾種方式來解決,不同的實(shí)現(xiàn)方式可能“麻煩”程度不一樣。具體的區(qū)別可以看下面的一個(gè)練習(xí)題:三線程交替打印 ABC 和生產(chǎn)者消費(fèi)者模型。(我都是使用信號(hào)量實(shí)現(xiàn)的,使用事件和條件也可以實(shí)現(xiàn),本質(zhì)是一樣的)
3.明白這些線程安全的方法,包括互斥鎖,自旋鎖,無鎖編程的適用的業(yè)務(wù)場(chǎng)景是什么?從OS和硬件角度說說原理是怎么樣的?開銷在哪里?
在多線程修改一個(gè)共享變量的場(chǎng)景下,如果不加鎖,很容易出現(xiàn)結(jié)果比于其的結(jié)果少那么幾個(gè)數(shù)。
加鎖的開銷,從os的角度來看就是需要頻繁的從用戶態(tài)進(jìn)入內(nèi)核態(tài)。
在申請(qǐng)鎖的時(shí)候,需要進(jìn)入內(nèi)核態(tài)申請(qǐng),如果申請(qǐng)到了,那么返回用戶態(tài)繼續(xù)執(zhí)行;如果沒有申請(qǐng)到,那么睡眠在內(nèi)核態(tài)。釋放鎖的時(shí)候,需要再一次進(jìn)入內(nèi)核態(tài),然后再返回用戶態(tài)。
4.能在現(xiàn)場(chǎng)借助cas操作,風(fēng)險(xiǎn)指針實(shí)現(xiàn)無鎖的數(shù)據(jù)結(jié)構(gòu),比如無鎖棧,無鎖環(huán)形隊(duì)列。無鎖排序鏈表
java 里面好像很愛講 cas操作,但是我不是很懂java。
c++里面是不是是原子操作std::atomic<int> + validate 關(guān)鍵字 ?
這個(gè)我不是很確定。
5. 幾個(gè)題目
5.1 三個(gè)線程輪流執(zhí)行順序打印ABC
使用信號(hào)量的機(jī)制 來實(shí)現(xiàn) 三個(gè)線程輪流執(zhí)行順序打印ABC:
# 使用信號(hào)量的機(jī)制 來實(shí)現(xiàn) 三個(gè)線程輪流執(zhí)行順序打印ABC
import threading
import time
sema_AtoB = threading.Semaphore(1)
sema_BtoC = threading.Semaphore(0)
sema_CtoA = threading.Semaphore(0)
def printA():
while(True):
sema_AtoB.acquire()
print ("A")
sema_BtoC.release()
def printB():
while(True):
sema_BtoC.acquire()
print ("B")
sema_CtoA.release()
def printC():
while(True):
sema_CtoA.acquire()
print ("C")
sema_AtoB.release()
threadlist=[]
threadlist.append(threading.Thread(target=printA))
threadlist.append(threading.Thread(target=printB))
threadlist.append(threading.Thread(target=printC))
for i in threadlist:
i.start()
for i in threadlist:
i.join()
使用信號(hào)量實(shí)現(xiàn)生產(chǎn)者--消費(fèi)者模型
import threading
import time
# 生產(chǎn)者的最大容量
max_capacity = 10
sema = threading.Semaphore(max_capacity)
def consumer():
times =25
while(times > 0):
sema.acquire()
print("消費(fèi)了一個(gè),剩下 ",sema._value)
times -=1
def producter():
times =20
while(times > 0):
sema.release()
print("生產(chǎn)了一個(gè),剩下 ",sema._value)
times -=1
threadlist=[]
threadlist.append(threading.Thread(target=consumer))
threadlist.append(threading.Thread(target=producter))
for i in threadlist:
i.start()
for i in threadlist:
i.join()
上面這兩個(gè)例子,使用條件condition 和 事件event 都可以是實(shí)現(xiàn)。本質(zhì)上他們都是線程同步的一種機(jī)制。
5.2 設(shè)計(jì)一個(gè)線程安全的隊(duì)列
https://harveyqing.gitbooks.io/python-read-and-write/content/python_basic/fifo_queue.html
to-do
設(shè)計(jì)一個(gè)不需要加鎖的線程安全的隊(duì)列
原子操作 std::atomic<int> + volidation ?
參考文獻(xiàn) :
基于python 的鎖:
https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_thread_sync.html
線程同步的方式:
http://yoyzhou.github.io/blog/2013/02/28/python-threads-synchronization-locks/
加鎖的類型 :
http://www.cnblogs.com/SealedLove/archive/2009/02/19/1393755.html
自旋鎖:
http://www.cnblogs.com/cposture/p/SpinLock.html
cas無鎖編程(compare and set):
http://blog.csdn.net/aesop_wubo/article/details/7537960
http://blog.jobbole.com/90811/
強(qiáng)烈推薦 :
http://www.dongwm.com/archives/使用Python進(jìn)行并發(fā)編程-線程篇/