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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一文读懂 | 进程并发与同步

發布時間:2023/12/20 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文读懂 | 进程并发与同步 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

并發?是指在某一時間段內能夠處理多個任務的能力,而?并行?是指同一時間能夠處理多個任務的能力。并發和并行看起來很像,但實際上是有區別的,如下圖(圖片來源于網絡):

concurrency-parallelism

上圖的意思是,有兩條在排隊買咖啡的隊列,并發只有一架咖啡機在處理,而并行就有兩架的咖啡機在處理。咖啡機的數量越多,并行能力就越強。

可以把上面的兩條隊列看成兩個進程,并發就是指只有單個CPU在處理,而并行就有兩個CPU在處理。為了讓兩個進程在單核CPU中也能得到執行,一般的做法就是讓每個進程交替執行一段時間,比如讓每個進程固定執行?100毫秒,執行時間使用完后切換到其他進程執行。而并行就沒有這種問題,因為有兩個CPU,所以兩個進程可以同時執行。如下圖:

concurrency-parallelism

原子操作

上面介紹過,并發有可能會打斷當前執行的進程,然后替切換成其他進程執行。如果有兩個進程同時對一個共享變量?count?進行加一操作,由于C語言的?count++?操作會被翻譯成如下指令:

mov eax, [count] inc eax mov [count], eax

那么在并發的情況下,有可能出現如下問題:

concurrency-problem

假設count變量初始值為0:

  • 進程1執行完?mov eax, [count]?后,寄存器eax內保存了count的值0。

  • 進程2被調度執行。進程2執行?count++?的所有指令,將累加后的count值1寫回到內存。

  • 進程1再次被調度執行,計算count的累加值仍為1,寫回到內存。

雖然進程1和進程2執行了兩次?count++?操作,但是count最后的值為1,而不是2。

要解決這個問題就需要使用?原子操作,原子操作是指不能被打斷的操作,在單核CPU中,一條指令就是原子操作。比如上面的問題可以把?count++?語句翻譯成指令?inc [count]?即可。Linux也提供了這樣的原子操作,如對整數加一操作的?atomic_inc():

static?__inline__?void?atomic_inc(atomic_t?*v) {__asm__?__volatile__(LOCK?"incl?%0":"=m"?(v->counter):"m"?(v->counter)); }

在多核CPU中,一條指令也不一定是原子操作,比如?inc [count]?指令在多核CPU中需要進行如下過程:

  • 從內存將count的數據讀取到cpu。

  • 累加讀取的值。

  • 將修改的值寫回count內存。

  • Intel x86 CPU?提供了?lock?前綴來鎖住總線,可以讓指令保證不被其他CPU中斷,如下:

    lock inc [count]

    原子操作?能夠保證操作不被其他進程干擾,但有時候一個復雜的操作需要由多條指令來實現,那么就不能使用原子操作了,這時候可以使用?鎖?來實現。

    計算機科學中的?鎖?與日常生活的?鎖?有點類似,舉個例子:比如要上公廁,首先找到一個沒有人的廁所,然后把廁所門鎖上。其他人要使用的話,必須等待當前這人使用完畢,并且把門鎖打開才能使用。在計算機中,要對某個公共資源進行操作時,必須對公共資源進行上鎖,然后才能使用。如果不上鎖,那么就可能導致數據混亂的情況。

    在Linux內核中,比較常用的鎖有:自旋鎖、信號量、讀寫鎖?等,下面介紹一下自旋鎖和信號量的實現。

    自旋鎖

    自旋鎖?只能在多核CPU系統中,其核心原理是?原子操作,原理如下圖:

    spinlock

    使用?自旋鎖?時,必須先對自旋鎖進行初始化(設置為1),上鎖過程如下:

  • 對自旋鎖?lock?進行減一操作,判斷結果是否等于0,如果是表示上鎖成功并返回。

  • 如果不等于0,表示其他進程已經上鎖,此時必須不斷比較自旋鎖?lock?的值是否等于1(表示已經解鎖)。

  • 如果自旋鎖?lock?等于1,跳轉到第一步繼續進行上鎖操作。

  • 由于Linux的自旋鎖使用匯編實現,所以比較苦澀難懂,這里使用C語言來模擬一下:

    void?spin_lock(amtoic_t?*lock) { again:result?=?--(*lock);if?(result?==?0)?{return;}while?(true)?{if?(*lock?==?1)?{goto?again;}} }

    上面代碼將?result = --(*lock);?當成原子操作,解鎖過程只需要把?lock?設置為1即可。由于自旋鎖會不斷嘗試上鎖操作,并不會對進程進行調度,所以在單核CPU中可能會導致 100% 的CPU占用率。另外,自旋鎖只適合粒度比較小的操作,如果操作粒度比較大,就需要使用信號量這種可調度進程的鎖。

    信號量

    與?自旋鎖?不一樣,當當前進程對?信號量?進行上鎖時,如果其他進程已經對其進行上鎖,那么當前進程會進入睡眠狀態,等待其他進程對信號量進行解鎖。過程如下圖:

    semaphore

    在Linux內核中,信號量使用?struct semaphore?表示,定義如下:

    struct?semaphore?{raw_spinlock_t??????lock;unsigned?int????????count;struct?list_head????wait_list; };

    各個字段的作用如下:

    • lock:自旋鎖,用于對多核CPU平臺進行同步。

    • count:信號量的計數器,上鎖時對其進行減一操作(count--),如果得到的結果為大于等于0,表示成功上鎖,如果小于0表示已經被其他進程上鎖。

    • wait_list:正在等待信號量解鎖的進程隊列。

    信號量?上鎖通過?down()?函數實現,代碼如下:

    void?down(struct?semaphore?*sem) {unsigned?long?flags;spin_lock_irqsave(&sem->lock,?flags);if?(likely(sem->count?>?0))sem->count--;else__down(sem);spin_unlock_irqrestore(&sem->lock,?flags); }

    上面代碼可以看出,down()?函數首先對信號量進行自旋鎖操作(為了避免多核CPU競爭),然后比較計數器是否大于0,如果是對計數器進行減一操作,并且返回,否則調用?__down()?函數進行下一步操作。__down()?函數實現如下:

    static?noinline?void?__sched?__down(struct?semaphore?*sem) {__down_common(sem,?TASK_UNINTERRUPTIBLE,?MAX_SCHEDULE_TIMEOUT); }static?inline?int?__down_common(struct?semaphore?*sem,long?state,?long?timeout) {struct?task_struct?*task?=?current;struct?semaphore_waiter?waiter;//?把當前進程添加到等待隊列中list_add_tail(&waiter.list,?&sem->wait_list);waiter.task?=?task;waiter.up?=?0;for?(;;)?{...__set_task_state(task,?state);spin_unlock_irq(&sem->lock);timeout?=?schedule_timeout(timeout);spin_lock_irq(&sem->lock);if?(waiter.up)?//?當前進程是否獲得信號量鎖?return?0;}... }

    __down()?函數最終調用?__down_common()?函數,而?__down_common()?函數的操作過程如下:

  • 把當前進程添加到信號量的等待隊列中。

  • 切換到其他進程運行,直到被其他進程喚醒。

  • 如果當前進程獲得信號量鎖(由解鎖進程傳遞),那么函數返回。

  • 接下來看看解鎖過程,解鎖過程主要通過?up()?函數實現,代碼如下:

    void?up(struct?semaphore?*sem) {unsigned?long?flags;raw_spin_lock_irqsave(&sem->lock,?flags);if?(likely(list_empty(&sem->wait_list)))?//?如果沒有等待的進程, 直接對計數器加一操作sem->count++;else__up(sem);?//?如果有等待進程,?那么調用?__up()?函數進行喚醒raw_spin_unlock_irqrestore(&sem->lock,?flags); }static?noinline?void?__sched?__up(struct?semaphore?*sem) {//?獲取到等待隊列的第一個進程struct?semaphore_waiter?*waiter?=?list_first_entry(&sem->wait_list,?struct?semaphore_waiter,?list);list_del(&waiter->list);???????//?把進程從等待隊列中刪除waiter->up?=?1;????????????????//?告訴進程已經獲得信號量鎖wake_up_process(waiter->task);?//?喚醒進程 }

    解鎖過程如下:

  • 判斷當前信號量是否有等待的進程,如果沒有等待的進程, 直接對計數器加一操作

  • 如果有等待的進程,那么獲取到等待隊列的第一個進程。

  • 把進程從等待隊列中刪除。

  • 告訴進程已經獲得信號量鎖

  • 喚醒進程。


  • 推薦閱讀:

    專輯|Linux文章匯總

    專輯|程序人生

    專輯|C語言

    我的知識小密圈

    關注公眾號,后臺回復「1024」獲取學習資料網盤鏈接。

    歡迎點贊,關注,轉發,在看,您的每一次鼓勵,我都將銘記于心~

    嵌入式Linux

    微信掃描二維碼,關注我的公眾號

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的一文读懂 | 进程并发与同步的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。