0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么情況下避免使用系統(tǒng)調(diào)用

科技綠洲 ? 來(lái)源:Linux開(kāi)發(fā)架構(gòu)之路 ? 作者:Linux開(kāi)發(fā)架構(gòu)之路 ? 2023-11-13 10:32 ? 次閱讀

linux多線程環(huán)境下對(duì)同一變量進(jìn)行讀寫(xiě)時(shí),經(jīng)常會(huì)遇到讀寫(xiě)的原子性問(wèn)題,即會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。為了解決多個(gè)線程對(duì)同一變量訪問(wèn)時(shí)的競(jìng)爭(zhēng)條件問(wèn)題,操作系統(tǒng)層面提供了鎖、信號(hào)量、條件變量等幾種線程同步機(jī)制。如果對(duì)變量的每次訪問(wèn)都使用上述機(jī)制,由于系統(tǒng)調(diào)用會(huì)陷入內(nèi)核空間,需要頻繁的進(jìn)行上下文切換,這就導(dǎo)致了程序的時(shí)間開(kāi)銷比較大。

自然的,我們就想到,在多線程環(huán)境中,在某些情況下是否能減少甚至避免使用系統(tǒng)調(diào)用?答案是肯定的。

如果對(duì)多線程下的變量訪問(wèn)進(jìn)行分析,可以看到,線程對(duì)變量的訪問(wèn)可以分為以下幾類:

  • 一個(gè)線程寫(xiě),另一個(gè)線程讀,簡(jiǎn)稱一寫(xiě)一讀
  • 多個(gè)線程寫(xiě),一個(gè)線程讀,簡(jiǎn)稱多寫(xiě)一讀
  • 一個(gè)線程寫(xiě),多個(gè)線程讀,簡(jiǎn)稱一寫(xiě)多讀。
  • 多個(gè)線程寫(xiě),多個(gè)線程讀,簡(jiǎn)稱多寫(xiě)多讀。

在linux 系統(tǒng)中,多個(gè)線程同時(shí)讀一個(gè)變量是不需要同步的,而多個(gè)線程同時(shí)寫(xiě)一個(gè)變量或一個(gè)線程寫(xiě)而其他線程讀某個(gè)變量,是需要同步的,可以總結(jié)為:”多讀不互斥,而讀寫(xiě)和多寫(xiě)互斥“。

由于多個(gè)線程對(duì)同一變量的讀不需要同步,因而一寫(xiě)多讀和一寫(xiě)一讀并無(wú)本質(zhì)區(qū)別,進(jìn)而可以把多線程下對(duì)變量訪問(wèn)依據(jù)是否需要同步而合并成如下三類:

  • 一寫(xiě)多讀
  • 多寫(xiě)一讀
  • 多寫(xiě)多讀

解決上面所有的互斥,都可以使用系統(tǒng)調(diào)用。上面已經(jīng)提到,在某些情況下我們是可以避免使用代價(jià)高昂的系統(tǒng)調(diào)用的。而“一寫(xiě)多讀”就是這些特殊情況中的一種。

雙buffer “無(wú)鎖” 設(shè)計(jì)

使用系統(tǒng)調(diào)用進(jìn)行同步的主要問(wèn)題在于頻繁切換上下文耗時(shí)較長(zhǎng),而后臺(tái)系統(tǒng)的處理速度又是除正確性之外最為關(guān)鍵的指標(biāo)。為提高系統(tǒng)的運(yùn)行速度,我們可以使用用其他系統(tǒng)資源來(lái)?yè)Q取時(shí)間的辦法,從而避免使用鎖之類系統(tǒng)調(diào)用。在這些方法中,最常見(jiàn)的就是用空間換取時(shí)間。

針對(duì)一寫(xiě)多讀的情況,可以使用”雙 buffer“ 及共享指針機(jī)制來(lái)實(shí)現(xiàn)對(duì)同一變量高效訪問(wèn),同時(shí)又能保證不會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。這一實(shí)現(xiàn)的技術(shù)關(guān)鍵點(diǎn)在于以下兩個(gè)方面:

  • 雙 buffer 的備份機(jī)制,避免了同時(shí)讀寫(xiě)同一變量。雙buffer 就是指對(duì)于通常要被多個(gè)線程訪問(wèn)的變量,再額外定義一個(gè)備份變量。由于是一寫(xiě)多讀,寫(xiě)線程只向備份變量中寫(xiě)入,而所有的讀線程只需要訪問(wèn)主變量本身即可。當(dāng)寫(xiě)進(jìn)程對(duì)備份變量的寫(xiě)操作完成后,會(huì)觸發(fā)主變量指針和備份變量指針的互換操作,即指針切換,從而將原變量和備份變量的身份進(jìn)行互換,達(dá)到數(shù)據(jù)更新的目的。
  • 共享指針 shared_ptr,由于其記錄了對(duì)變量的引用次數(shù),因而可以避免指針切換時(shí)的“訪問(wèn)丟失”問(wèn)題。

為了便于理解,本文使用 C++ 中的 map 類型變量作為示意,當(dāng)然,本文的方法可以推廣到一寫(xiě)多讀模式下任意數(shù)據(jù)類型的更新中。使用雙 buffer 的示意圖如下:

圖片

注意ptr 和 bak_ptr 都是整個(gè)map 的指針,上面藍(lán)色箭頭表示通過(guò)兩個(gè)指針訪問(wèn) map 中的元素,ptr 和bak_ptr 本身并不指向元素。

在系統(tǒng)啟動(dòng)時(shí),把兩個(gè)智能指針?lè)謩e初始化為一個(gè)主map 和一個(gè)備份 map。之后把全部數(shù)據(jù)更新到主map中開(kāi)始對(duì)外提供服務(wù)。當(dāng)外部需要讀取數(shù)據(jù)時(shí)(多讀),全部通過(guò)主map 的智能指針 ptr 來(lái)實(shí)現(xiàn)。而數(shù)據(jù)的更新全部通過(guò)備份map 的指針bak_ptr 來(lái)實(shí)現(xiàn)。由此可以看出,由于使用了兩個(gè)map,即雙buffer,使得數(shù)據(jù)的讀和寫(xiě)進(jìn)行了分離,互不影響,不會(huì)出現(xiàn)競(jìng)爭(zhēng)條件,避免了鎖的使用。

指針的切換

由于讀寫(xiě)分離,雙buffer機(jī)制下的數(shù)據(jù)讀寫(xiě)不會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。在備份map 中數(shù)據(jù)更新完成時(shí),必然需要一種方式,使得新數(shù)據(jù)能被使用到。這里需要做的就是把主map和備份map 的共享指針指向的內(nèi)容互換,即ptr 和bak_ptr 指向的內(nèi)容互換。指針切換如下圖所示:

圖片

那么,在指針互換時(shí),會(huì)出現(xiàn)什么問(wèn)題呢?

在指針的切換過(guò)程中,會(huì)出現(xiàn)如下兩個(gè)問(wèn)題:

  • 由于對(duì)主map 的讀是多線程的讀,會(huì)出現(xiàn)多線程同使用主map 共享指針ptr 的情形,而互換指針時(shí),需要對(duì)主map 的指針進(jìn)行寫(xiě)操作,那么對(duì)同一指針 ptr 的讀和寫(xiě)的競(jìng)爭(zhēng)條件如何解決?
  • 在準(zhǔn)備互換ptr 和 bak_ptr 指向的內(nèi)容時(shí),如果某個(gè)讀線程正在使用 ptr 訪問(wèn)主map,直接互換就可能出現(xiàn)讀線程再通過(guò)ptr獲取數(shù)據(jù)時(shí)訪問(wèn)失效的問(wèn)題,嚴(yán)重的情況下會(huì)訪問(wèn)到無(wú)效內(nèi)存導(dǎo)致程序崩潰。這一問(wèn)題本文簡(jiǎn)稱為”指針訪問(wèn)丟失“問(wèn)題,類似于常規(guī)指針中出現(xiàn)的野指針或懸垂指針的問(wèn)題。

ptr 競(jìng)爭(zhēng)條件的解決

當(dāng)指針切換時(shí),單線程對(duì) bak_ptr 的寫(xiě)操作已經(jīng)完成,因而對(duì)其可以隨便讀寫(xiě)。但由于多個(gè)讀線程可能還在使用ptr,切換指針時(shí)對(duì) ptr 的讀寫(xiě)就要十分的小心。為了避免對(duì) ptr 的讀寫(xiě)出現(xiàn)競(jìng)爭(zhēng)條件,本文使用了自旋鎖來(lái)對(duì)ptr 的讀寫(xiě)進(jìn)行同步。使用自旋鎖的原因有兩個(gè):

  • 只在指針切換時(shí)使用鎖,而不是在讀寫(xiě)兩個(gè)map 時(shí)使用鎖,因而鎖的使用頻率會(huì)非常的低,由此導(dǎo)致的上下文切換的代價(jià)是可接受的。
  • 由于指針切換時(shí) ptr 處于的情形是一寫(xiě)多讀,指針互換準(zhǔn)備對(duì) ptr 進(jìn)行寫(xiě)操作時(shí),要獲取鎖的等待時(shí)間并不長(zhǎng),并不會(huì)有長(zhǎng)時(shí)間的鎖等待出現(xiàn),因而可以使用代價(jià)更小的自旋鎖,而不是使用代價(jià)更高的讀寫(xiě)鎖。

指針訪問(wèn)丟失

上面已經(jīng)介紹了指針訪問(wèn)丟失的情形,即在兩個(gè)指針切換時(shí),多個(gè)讀線程可能正在使用ptr。為了避免出現(xiàn)讀線程會(huì)讀取到無(wú)效數(shù)據(jù),本文使用的方法是利用共享指針的引用計(jì)數(shù)來(lái)實(shí)現(xiàn)指針的延遲互換。

解決ptr 的競(jìng)爭(zhēng)條件和指針訪問(wèn)丟失問(wèn)題后,就可以安全的使用雙buffer 方案了。

最終的代碼如下,其中 mapptr 就是主map 指針,bakptr 是備份map 的指針:

class UpdateData {
  public:
    UpdateData():flag_(0) {
    }

    void PeriodTask();
    void SetFlag(int i) {
      flag_ = i;
    }
  private:
    shared_ptr< map > map_ptr_;
    SpinLock map_rwspinlock_;
    shared_ptr< map > bak_map_ptr_;
    int flag_;

    shared_ptr< map > GetMainMapPtr(); 
    void SetMainMapPtr(shared_ptr< map > new_map_ptr);
    void SwitchMapPtr();
    void PeriodTask();
    void GetData(shared_ptr< map > ptr) {
      ptr["abc"] = "def";
      ...
    }
};

// 獲取主map 指針
shared_ptr< map > UpdateData::GetMainMapPtr() {
  Lock(map_rwspinlock_); // 加自旋鎖,避免對(duì) ptr 訪問(wèn)出現(xiàn)競(jìng)爭(zhēng)條件
  return map_ptr_;  // 主map 指針
}

// 設(shè)置主map 指針
void UpdateData::SetMainMapPtr(shared_ptr< map > new_map_ptr) {
  Lock(map_rwspinlock_);  // 加自旋鎖,避免對(duì) ptr 訪問(wèn)出現(xiàn)競(jìng)爭(zhēng)條件
  map_ptr_ = new_map_ptr;
}

// 真正的切換指針
void UpdateData::SwitchMapPtr() {
  shared_ptr< map > old_map_ptr = GetMainMapPtr();
  SetMainMapPtr(bak_ptr_);  // 這里新數(shù)據(jù)已經(jīng)可以被使用了

  // 用引用次數(shù)來(lái)解決訪問(wèn)丟失問(wèn)題
  while (old_map_ptr.unique() {
    ::usleep(10000);  // 指針延遲互換
  }
  bak_map_ptr_ = old_map_ptr;
  bak_map_ptr_- >clear();
}


// 定時(shí)任務(wù)
void UpdateData::PeriodTask() {
    while(flag) {
      ::sleep(300); // 每5分鐘更新一次數(shù)據(jù)
      GetData(bak_ptr_); // 新數(shù)據(jù)寫(xiě)到備份 map 中
      SwitchMapPtr();
    }
}

需要注意的是,SwitchMapPtr 中調(diào)用 SetMainMapPtr(bakptr) 之后,即使程序一直處在while 循環(huán)中,再有新的線程通過(guò) mapptr 來(lái)訪問(wèn)主map 的數(shù)據(jù)時(shí),使用的已經(jīng)是新的數(shù)據(jù)了。while 循環(huán)是為了解決指針訪問(wèn)丟失問(wèn)題。當(dāng)引用次數(shù)為1時(shí),即 unique 為真時(shí),表示已經(jīng)沒(méi)有讀線程再使用舊的 map 了,只剩下SwitchMapPtr 中old_map_ptr 這一個(gè)引用了,這時(shí)可以安全的釋放舊的map,并把它清空當(dāng)作備份map繼續(xù)進(jìn)行數(shù)據(jù)的更新操作。

從上面可以看出,通過(guò)使用雙buffer和共享指針,避免了在一寫(xiě)多讀模式中對(duì)數(shù)據(jù)的讀寫(xiě)頻繁加鎖,實(shí)現(xiàn)了”無(wú)鎖“ 的設(shè)計(jì)。

延伸

即然雙buffer可以很好的用于一寫(xiě)多讀模式,那么對(duì)于”多寫(xiě)一讀“或”多寫(xiě)多讀“模式,是否也可以引入雙buffer 模式呢?

在含有多線程寫(xiě)同一變量的情形下下,其實(shí)是不太適合使用雙buffer 方案的。主要原因是:

  • 多寫(xiě)的情形下,需要在 bak_map 的多個(gè)寫(xiě)操作之間通過(guò)鎖來(lái)同步,雖然避免了對(duì)讀寫(xiě)互斥情形的加鎖,但是多線程寫(xiě)時(shí)通常對(duì)數(shù)據(jù)的實(shí)時(shí)性要求較高,如果使用雙buffer,所有新數(shù)據(jù)必須要等到指針切換時(shí)才能被使用,很可能達(dá)不到實(shí)時(shí)性要求。
  • 多線程寫(xiě)時(shí)若用雙buffer,則在指針切換時(shí)也需要給bak_map 加鎖,并且也要用類似于上面的while 循環(huán)來(lái)保證沒(méi)有線程在執(zhí)行寫(xiě)入操作時(shí)才能進(jìn)行指針切換,而且此時(shí)也要等待多讀的完成才能進(jìn)行切換,這時(shí)就會(huì)出現(xiàn)對(duì) bak_map 的鎖定時(shí)間過(guò)長(zhǎng),在數(shù)據(jù)更新頻繁的情況下是不合適的。

因而,在多寫(xiě)的模式下,還是優(yōu)先用讀寫(xiě)鎖等操作系統(tǒng)提供的同步機(jī)制。

結(jié)語(yǔ)

雙buffer 方案在多線程環(huán)境下能較好的解決 “一寫(xiě)多讀” 時(shí)的數(shù)據(jù)更新問(wèn)題,特別是適用于數(shù)據(jù)需要定期更新,且一次更新數(shù)據(jù)量較大的情形。而這種情形在后臺(tái)開(kāi)發(fā)中十分常見(jiàn)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11310

    瀏覽量

    209616
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6834

    瀏覽量

    123350
  • 多線程
    +關(guān)注

    關(guān)注

    0

    文章

    278

    瀏覽量

    19981
  • 系統(tǒng)調(diào)用

    關(guān)注

    0

    文章

    28

    瀏覽量

    8328
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    什么情況下選用PCI板卡?什么情況下選用PXI板卡?

    測(cè)控選板卡方案時(shí),什么情況下選PCI,什么情況下選PXI?
    發(fā)表于 03-31 20:52

    什么情況下使用DMA?如何去使用DMA

    什么是DMA?DMA的內(nèi)部結(jié)構(gòu)是由哪些部分組成的?什么情況下使用DMA?如何去使用DMA?
    發(fā)表于 10-11 06:01

    ch395什么情況下多個(gè)udp包會(huì)被合并,可否避免?

    產(chǎn)生數(shù)據(jù)長(zhǎng)度為0的接收中斷3. 什么情況下多個(gè)udp包會(huì)被合并,可否避免??我的工況是不希望udp包被分組,也不希望包合并該如何處理?
    發(fā)表于 10-12 07:25

    什么情況下數(shù)據(jù)能恢復(fù)和不能恢復(fù)

    什么情況下數(shù)據(jù)能恢復(fù)和不能恢復(fù) 因數(shù)據(jù)的破壞復(fù)雜多變,數(shù)據(jù)能否恢復(fù)或幾率的大小不能一概而論。請(qǐng)查閱本站相關(guān)文章了解數(shù)
    發(fā)表于 03-29 10:42 ?2836次閱讀

    volte語(yǔ)音通話有什么用,什么情況下可以開(kāi)/關(guān)volte

    聽(tīng)很多人都在講volte高清語(yǔ)音通話那么什么是vote呢?什么情況下需要開(kāi)通volte呢?什么情況下又可以關(guān)閉volte呢?
    發(fā)表于 10-21 16:36 ?1.7w次閱讀

    什么情況下我們才會(huì)使用邊沿信號(hào)?

    很多從事PLC編程的朋友都知道,不管是什么品牌的PLC,都有上升沿和下降沿指令。 那么什么情況下我們才會(huì)使用或必須使用邊沿信號(hào)呢?邊沿信號(hào)我們又如何獲取呢? 如圖1,任何一個(gè)開(kāi)關(guān)信號(hào)(或數(shù)字信號(hào)
    的頭像 發(fā)表于 05-03 10:14 ?4425次閱讀
    <b class='flag-5'>什么情況下</b>我們才會(huì)使用邊沿信號(hào)?

    什么情況下使用示波器

    示波器可以把我們看不見(jiàn)的電信號(hào)變換成看得見(jiàn)的圖像,方便來(lái)研究各種電現(xiàn)象的變化過(guò)程。那么什么情況下使用示波器呢? 使用示波器進(jìn)行測(cè)量需要涂有熒光物質(zhì)的屏面、主機(jī)、探頭配置和穩(wěn)定的信號(hào)。示波器一般都是
    的頭像 發(fā)表于 02-01 11:00 ?5727次閱讀

    什么情況下要進(jìn)行電能質(zhì)量檢測(cè)?

    什么情況下要進(jìn)行電能質(zhì)量檢測(cè)?
    發(fā)表于 09-08 14:20 ?690次閱讀

    什么情況下選用工業(yè)主板

    雖然工業(yè)主板和普通主板差異比較多,但是在某些情況下工業(yè)主板用于商業(yè)環(huán)境也是可以的,但是實(shí)用性不是很好。什么情況下選用工業(yè)主板呢?
    的頭像 發(fā)表于 02-14 10:34 ?861次閱讀
    <b class='flag-5'>什么情況下</b>選用工業(yè)主板

    什么情況下需要使用微機(jī)消諧裝置

    什么情況下需要使用微機(jī)消諧裝置 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 微機(jī)消諧裝置是一種電力系統(tǒng)保護(hù)設(shè)備,用于消除諧波及其帶來(lái)的負(fù)面影響。下面是一些需要使用微機(jī)消諧裝置
    的頭像 發(fā)表于 03-06 13:46 ?677次閱讀

    電機(jī)什么情況下需要配減速機(jī)?

    電機(jī)是運(yùn)動(dòng)機(jī)械的最常用動(dòng)力源,今天分享什么情況下電機(jī)需要配減速機(jī)。
    的頭像 發(fā)表于 05-26 17:47 ?1924次閱讀
    電機(jī)<b class='flag-5'>什么情況下</b>需要配減速機(jī)?

    電機(jī)什么情況下需要配減速機(jī)呢

    電機(jī)是運(yùn)動(dòng)機(jī)械的最常用動(dòng)力源,今天分享什么情況下電機(jī)需要配減速機(jī)。
    的頭像 發(fā)表于 07-21 17:31 ?1539次閱讀
    電機(jī)<b class='flag-5'>什么情況下</b>需要配減速機(jī)呢

    應(yīng)急燈什么情況下才會(huì)亮?

    應(yīng)急燈什么情況下才會(huì)亮? 應(yīng)急燈指的是電源發(fā)生故障時(shí),正常照明無(wú)法使用的情況下,啟動(dòng)的照明燈。比如說(shuō)因?yàn)榛馂?zāi)導(dǎo)致正常照明系統(tǒng)失效時(shí),消防疏散照明燈、消防應(yīng)急照明燈會(huì)自動(dòng)亮起,起到疏散人群,提供照明
    的頭像 發(fā)表于 07-25 13:57 ?1.5w次閱讀

    什么情況下選擇熱電偶?什么情況下選擇熱電阻?哪個(gè)更合適?

    什么情況下選擇熱電偶?什么情況下選擇熱電阻?哪個(gè)更合適? 熱電偶和熱電阻都是溫度傳感器,用于測(cè)量溫度。但是它們的原理、特性和應(yīng)用場(chǎng)景不同。在實(shí)際選擇中,需要根據(jù)具體情況,選擇更合適的一種。 1.
    的頭像 發(fā)表于 10-26 17:47 ?1735次閱讀

    什么情況下電容器會(huì)被擊穿

    電容器是一種常見(jiàn)的電子元件,廣泛應(yīng)用于各個(gè)領(lǐng)域。然而,在特定條件,電容器可能會(huì)發(fā)生擊穿現(xiàn)象,導(dǎo)致其無(wú)法正常工作甚至損壞。那么,在什么情況下電容器會(huì)被擊穿呢?
    的頭像 發(fā)表于 02-19 14:11 ?2760次閱讀