怎么樣才算得上是熟悉多線程編程?

在知乎上面看見一個(gè)很意思的問題,怎么樣才算是熟悉多線程編程?
https://www.zhihu.com/question/22375509 看了很多人的分享,整理一下大家的答案。

1.明白多進(jìn)程和多線程的基本概念。

  1. 多進(jìn)程之間的內(nèi)存空間是獨(dú)立的,所以多進(jìn)程之間多用的通信,而不是加鎖(因?yàn)樽兞康膬?nèi)存空間是獨(dú)立的,要加鎖也是“大鎖”,比為文件鎖這類的)。

  2. 進(jìn)程之間通信的機(jī)制?
    socket,管道,消息隊(duì)列,共享內(nèi)存,信號(hào)(比如 在terminal 輸入 kill -9 就是發(fā)送kill信號(hào))

  3. 線程之間的為什么要加鎖?如果不加鎖會(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)程之間一般討論的是通信,線程之間討論的是如何同步,線程互斥和同步的方式有:
互斥:

  1. 鎖(各種類型的加鎖) ;

同步:

  1. 信號(hào)量 Semaphores(可以理解是高級(jí)的鎖,因?yàn)閮?nèi)部的實(shí)現(xiàn)就是一個(gè)帶有計(jì)數(shù)的鎖);
  2. 事件 event
  3. 條件 Conditions

  1. 線程加鎖的類型
    1.1 mutex 互斥鎖
    1.2 遞歸鎖 / python 里面叫做 RLock
    1.3 讀寫鎖
    1.4 自旋鎖

  2. 信號(hào)量 Semaphores
    可以理解是高級(jí)的鎖,因?yàn)閮?nèi)部的實(shí)現(xiàn)就是一個(gè)帶有計(jì)數(shù)的鎖

  3. event
    基于事件的同步是指:一個(gè)線程發(fā)送/傳遞事件,另外的線程等待事件的觸發(fā)。

  4. 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ā)編程-線程篇/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容