女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何用C++11實現自旋鎖

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-11 16:48 ? 次閱讀

下面我會分析一下自旋鎖,并代碼實現自旋鎖和互斥鎖的性能對比,以及利用C++11實現自旋鎖。

一:自旋鎖(spin lock)

自旋鎖是一種用于保護多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當自旋鎖嘗試獲取鎖時以忙等待(busy waiting)的形式不斷地循環檢查鎖是否可用。

在多CPU的環境中, 對持有鎖較短的程序來說,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。

最后加粗的句子很重要,本文將針對該結論進行驗證。

下面是man手冊中對自旋鎖pthread_spin_lock()函數的描述:

DESCRIPTION The pthread_spin_lock() function shall lock the spin lock referenced by lock. The calling thread shall acquire the lock if it is not held by another thread. Otherwise, the thread shall spin (that is, shall not return from the pthread_spin_lock() call) until the lock becomes available. The results are undefined if the calling thread holds the lock at the time the call is made. The pthread_spin_trylock() function shall lock the spin lock referenced by lock if it is not held by any thread. Otherwise, the function shall fail. The results are undefined if any of these functions is called with an uninitialized spin lock.

可以看出,自選鎖的主要特征:當自旋鎖被一個線程獲得時,它不能被其它線程獲得。如果其他線程嘗試去phtread_spin_lock()獲得該鎖,那么它將不會從該函數返回,而是一直自旋(spin),直到自旋鎖可用為止。

使用自旋鎖時要注意:

  • 由于自旋時不釋放CPU,因而持有自旋鎖的線程應該盡快釋放自旋鎖,否則等待該自旋鎖的線程會一直在哪里自旋,這就會浪費CPU時間。
  • 持有自旋鎖的線程在sleep之前應該釋放自旋鎖以便其他咸亨可以獲得該自旋鎖。內核編程中,如果持有自旋鎖的代碼sleep了就可能導致整個系統掛起。(下面會解釋)

使用任何鎖都需要消耗系統資源(內存資源和CPU時間),這種資源消耗可以分為兩類:

1.建立鎖所需要的資源

2.當線程被阻塞時所需要的資源

POSIX提供的與自旋鎖相關的函數有以下幾個,都在中。

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

初始化spin lock, 當線程使用該函數初始化一個未初始化或者被destroy過的spin lock有效。該函數會為spin lock申請資源并且初始化spin lock為unlocked狀態。

有關第二個選項是這么說的:

If the Thread Process-Shared Synchronization option is supported and the value of pshared is PTHREAD_PROCESS_SHARED, the implementation shall permit the spin lock to be operated upon by any thread that has access to the memory where the spin lock is allocated, even if it is allocated in memory that is shared by multiple processes. If the Thread Process-Shared Synchronization option is supported and the value of pshared is PTHREAD_PROCESS_PRIVATE, or if the option is not supported, the spin lock shall only be operated upon by threads created within the same process as the thread that initialized the spin lock. If threads of differing processes attempt to operate on such a spin lock, the behav‐ ior is undefined.

所以,如果初始化spin lock的線程設置第二個參數為PTHREAD_PROCESS_SHARED,那么該spin lock不僅被初始化線程所在的進程中所有線程看到,而且可以被其他進程中的線程看到,PTHREAD_PROESS_PRIVATE則只被同一進程中線程看到。如果不設置該參數,默認為后者。

int pthread_spin_destroy(pthread_spinlock_t *lock);

銷毀spin lock,作用和mutex的相關函數類似,就不翻譯了:

The pthread_spin_destroy() function shall destroy the spin lock referenced by lock and release any resources used by the lock. The effect of subsequent use of the lock is undefined until the lock is reinitialized by another call to pthread_spin_init(). The results are undefined if pthread_spin_destroy() is called when a thread holds the lock, or if this function is called with an uninitialized thread spin lock.

不過和mutex的destroy函數一樣有這樣的性質(當初害慘了我):

The result of referring to copies of that object in calls to pthread_spin_destroy(), pthread_spin_lock(), pthread_spin_try‐ lock(), or pthread_spin_unlock() is undefined.

int pthread_spin_lock(pthread_spinlock_t *lock);

加鎖函數,功能上文都說過了,不過這么一點值得注意:

EBUSY A thread currently holds the lock. These functions shall not return an error code of [EINTR].

int pthread_spin_trylock(pthread_spinlock_t *lock);

還有這個函數,這個一般很少用到。

int pthread_spin_unlock(pthread_spinlock_t *lock);

解鎖函數。不是持有鎖的線程調用或者解鎖一個沒有lock的spin lock這樣的行為都是undefined的。

二:自旋鎖和互斥鎖的區別

從實現原理上來講,Mutex屬于sleep-waiting類型的 鎖。例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0和Core1上。假設線程A想要通過 pthread_mutex_lock操作去得到一個臨界區的鎖,而此時這個鎖正被線程B所持有,那么線程A就會被阻塞(blocking),Core0 會在此時進行上下文切換(Context Switch)將線程A置于等待隊列中, 此時Core0就可以運行其他的任務(例如另一個線程C)而不必進行忙等待。而Spin lock則不然,它屬于busy-waiting類型的鎖,如果線程A是使用pthread_spin_lock操作去請求鎖,那么線程A就會一直在 Core0上進行忙等待并不停的進行鎖請求,直到得到這個鎖為止。

如果大家去查閱Linux glibc中對pthreads API的實現NPTL(Native POSIX Thread Library) 的源碼的話(使用”getconf GNU_LIBPTHREAD_VERSION”命令可以得到我們系統中NPTL的版本號),就會發現pthread_mutex_lock()操作如果 沒有鎖成功的話就會調用system_wait()的系統調用并將當前線程加入該mutex的等待隊列里。而spin lock則可以理解為在一個while(1)循環中用內嵌的匯編代碼實現的鎖操作(印象中看過一篇論文介紹說在linux內核中spin lock操作只需要兩條CPU指令,解鎖操作只用一條指令就可以完成)。有興趣的朋友可以參考另一個名為sanos的微內核中pthreds API的實現:mutex.c spinlock.c,盡管與NPTL中的代碼實現不盡相同,但是因為它的實現非常簡單易懂,對我們理解spin lock和mutex的特性還是很有幫助的。

對于自旋鎖來說,它只需要消耗很少的資源來建立鎖;隨后當線程被阻塞時,它就會一直重復檢查看鎖是否可用了,也就是說當自旋鎖處于等待狀態時它會一直消耗CPU時間。

對于互斥鎖來說,與自旋鎖相比它需要消耗大量的系統資源來建立鎖;隨后當線程被阻塞時,線程的調度狀態被修改,并且線程被加入等待線程隊列;最后當鎖可用 時,在獲取鎖之前,線程會被從等待隊列取出并更改其調度狀態;但是在線程被阻塞期間,它不消耗CPU資源。

因此自旋鎖和互斥鎖適用于不同的場景。自旋鎖適用于那些僅需要阻塞很短時間的場景,而互斥鎖適用于那些可能會阻塞很長時間的場景。

三:自旋鎖與linux內核進程調度關系

現在我們就來說一說之前的問題,如果臨界區可能包含引起睡眠的代碼則不能使用自旋鎖,否則可能引起死鎖:

那么為什么信號量保護的代碼可以睡眠而自旋鎖會死鎖呢?

先看下自旋鎖的實現方法吧,自旋鎖的基本形式如下:

spin_lock(&mr_lock):

    //critical region

    spin_unlock(&mr_lock);

跟蹤一下spin_lock(&mr_lock)的實現

#define spin_lock(lock) _spin_lock(lock)

#define _spin_lock(lock) __LOCK(lock)

#define __LOCK(lock)

do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

注意到“preempt_disable()”,這個調用的功能是“關搶占”(在spin_unlock中會重新開啟搶占功能)。從中可以看出,使用自旋鎖保護的區域是工作在非搶占的狀態;即使獲取不到鎖,在“自旋”狀態也是禁止搶占的。了解到這,我想咱們應該能夠理解為何自旋鎖保護 的代碼不能睡眠了。試想一下,如果在自旋鎖保護的代碼中間睡眠,此時發生進程調度,則可能另外一個進程會再次調用spinlock保護的這段代碼。而我們 現在知道了即使在獲取不到鎖的“自旋”狀態,也是禁止搶占的,而“自旋”又是動態的,不會再睡眠了,也就是說在這個處理器上不會再有進程調度發生了,那么 死鎖自然就發生了。

總結下自旋鎖的特點:

  • 單CPU非搶占內核下:自旋鎖會在編譯時被忽略(因為單CPU且非搶占模式情況下,不可能發生進程切換,時鐘只有一個進程處于臨界區(自旋鎖實際沒什么用了)
  • 單CPU搶占內核下:自選鎖僅僅當作一個設置搶占的開關(因為單CPU不可能有并發訪問臨界區的情況,禁止搶占就可以保證臨街區唯一被擁有)
  • 多CPU下:此時才能完全發揮自旋鎖的作用,自旋鎖在內核中主要用來防止多處理器中并發訪問臨界區,防止內核搶占造成的競爭。

四:linux發生搶占的時間

linux搶占發生的時間,搶占分為 用戶搶占和 內核搶占。

用戶搶占在以下情況下產生:

  • 從系統調用返回用戶空間
  • 從中斷處理程序返回用戶空間

內核搶占會發生在:

  • 當從中斷處理程序返回內核空間的時候,且當時內核具有可搶占性
  • 當內核代碼再一次具有可搶占性的時候(如:spin_unlock時)
  • 如果內核中的任務顯示的調用schedule() (這個我暫時不太懂)

基本的進程調度就是發生在時鐘中斷后,并且發現進程的時間片已經使用完了,則發生進程搶占。通常我們會利用中斷處理程序返回內核空間的時候可進行內核搶占這個特性來提高一些I/O操作的實時性,如:當I/O事件發生的時候,對應的中斷處理程序被激活,當它發現有進程在等待這個I/O事件的時候,它 會激活等待進程,并且設置當前正在執行進程的need_resched標志,這樣在中斷處理程序返回的時候,調度程序被激活,原來在等待I/O事件的進程 (很可能)獲得執行權,從而保證了對I/O事件的相對快速響應(毫秒級)。可以看出,在I/O事件發生的時候,I/O事件的處理進程會搶占當前進程,系統 的響應速度與調度時間片的長度無關。

五:spin_lock和mutex實際效率對比

1.++i是否需要加鎖?

我分別使用POSIX的spin_lock和mutex寫了兩個累加的程序,啟動了兩個線程,并利用時間戳計算它們執行完累加所用的時間。

下面這個是使用spin_lock的代碼,我啟動兩個線程同時對num進行++,使用spin_lock保護臨界區,實際上可能會有疑問++i(++i和++num本文中是一個意思)為什么還要加鎖?

i++需要加鎖是很明顯的事情,對i++的操作的印象是,它一般是三步曲,從內存中取出i放入寄存器中,在寄存器中對i執行inc操作,然后把i放回內存中。這三步明顯是可打斷的,所以需要加鎖。

但是++i可能就有點猶豫了。實際上印象流是不行的,來看一下i++和++i的匯編代碼,其實他們是一樣的,都是三步,我只上一個圖就行了,如下:

圖片

所以++i也不是原子操作,在多核的機器上,多個線程在讀取內存中的i時,可能讀取到同一個值,這就導致多個線程同時執行+1,但實際上它們得到的結果是一樣的,即i只加了一次。還有一點:這幾句匯編正說明了++i和i++i對于效率是一樣的,不過這只是針對內建POD類型而言,如果是class的話,我們都寫過類的++運算符的重載,如果一個類在單個語句中不寫++i,而是寫i++的話,那無疑效率會有很大的損耗。(有點跑題)

2.spin_lock代碼

首先是spin_lock實現兩個線程同時加一個數,每個線程均++num,然后計算花費的時間。

#include < iostream >
#include < thread >

#include < pthread.h >
#include < sys/time.h >
#include < unistd.h >

int num = 0;
pthread_spinlock_t spin_lock;

int64_t get_current_timestamp()
{
    struct timeval now = {0, 0};
    gettimeofday(&now, NULL);
    return now.tv_sec * 1000 * 1000 + now.tv_usec;
}

void thread_proc()
{
    for(int i=0; i< 100000000; ++i){
        pthread_spin_lock(&spin_lock);
        ++num;
        pthread_spin_unlock(&spin_lock);
    }   
}

int main()
{
    pthread_spin_init(&spin_lock, PTHREAD_PROCESS_PRIVATE);//maybe PHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED

    int64_t start = get_current_timestamp();

    std::thread t1(thread_proc), t2(thread_proc);
    t1.join();
    t2.join();

    std::cout< "num:"<

3.mutex代碼

#include < iostream >
#include < thread >

#include < pthread.h >
#include < sys/time.h >
#include < unistd.h >

int num = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int64_t get_current_timestamp()
{   
    struct timeval now = {0, 0}; 
    gettimeofday(&now, NULL);
    return now.tv_sec * 1000 * 1000 + now.tv_usec;
}

void thread_proc()
{
    for(int i=0; i< 1000000; ++i){
        pthread_mutex_lock(&mutex);
        ++num;
        pthread_mutex_unlock(&mutex);
    }   
}

int main()
{
    int64_t start = get_current_timestamp();
   std::thread t1(thread_proc), t2(thread_proc);
    t1.join();
    t2.join();
    std::cout< "num:"<

4.結果分析

得出的結果如圖,num是最終結果,cost是花費時間,單位為us,main2是使用spin lock,

圖片

顯然,在臨界區只有++num這一條語句的情況下,spin lock相對花費的時間短一些,實際上它們有可能接近的情況,取決于CPU的調度情況,但始終會是spin lock執行的效率在本情況中花費時間更少。

我修改了兩個程序中臨界區的代碼,改為:

for(int i=0; i< 1000000; ++i){
        pthread_spin_lock(&spin_lock);
        ++num;
        for(int i=0; i< 100; ++i){
            //do nothing
        }   
        pthread_spin_unlock(&spin_lock);
    }

另一個使用mutex的程序也加了這么一段,然后結果就與之前的情況大相徑庭了:

圖片

實驗結果是如此的明顯,僅僅是在臨界區內加了一個10圈的循環,spin lock就需要花費比mutex更長的時間了。

所以, spin lock雖然lock/unlock的性能更好(花費很少的CPU指令),但是它只適應于臨界區運行時間很短的場景。實際開發中,程序員如果對自己程序的鎖行為不是很了解,否則使用spin lock不是一個好主意。更保險的方法是使用mutex,如果對性能有進一步的要求,那么再考慮spin lock。

六:使用C++實現自主實現自旋鎖

由于前面原理已經很清楚了,現在直接給代碼如下:

#pragma once

#include < atomic >

class spin_lock {
private:
    std::atomic< bool > flag = ATOMIC_VAR_INIT(false);
public:
    spin_lock() = default;
    spin_lock(const spin_lock&) = delete;
    spin_lock& operator=(const spin_lock) = delete;
    void lock(){   //acquire spin lock
        bool expected = false;
        while(!flag.compare_exchange_strong(expected, true));
            expected = false;    
    }   
    void unlock(){   //release spin lock
        flag.store(false);
    }   
};

測試文件,僅給出關鍵部分:

int num = 0;
spin_lock sm; 

void thread_proc()
{
    for(int i=0; i< 10000000; ++i){
        sm.lock();
        ++num;
        sm.unlock();
    }   
}

好的,對自旋鎖的總結就先到這里了。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 多線程
    +關注

    關注

    0

    文章

    279

    瀏覽量

    20306
  • 函數
    +關注

    關注

    3

    文章

    4370

    瀏覽量

    64193
  • C++
    C++
    +關注

    關注

    22

    文章

    2117

    瀏覽量

    74778
  • 自旋鎖
    +關注

    關注

    0

    文章

    11

    瀏覽量

    1657
收藏 人收藏

    評論

    相關推薦
    熱點推薦

    深度解析自旋自旋實現方案

    入場券自旋和MCS自旋都屬于排隊自旋(queued spinlock),進程按照申請
    發表于 09-19 11:39 ?4628次閱讀
    深度解析<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b>及<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b>的<b class='flag-5'>實現</b>方案

    使用C++11新特性實現一個通用的線程池設計

    C++11標準之前,多線程編程只能使用pthread_xxx開頭的一組POSIX標準的接口。從C++11標準開始,多線程相關接口封裝在了C++的std命名空間里。
    的頭像 發表于 12-22 13:58 ?1884次閱讀
    使用<b class='flag-5'>C++11</b>新特性<b class='flag-5'>實現</b>一個通用的線程池設計

    Linux驅動開發筆記-自旋和信號量

    !案例:利用自旋實現一個設備只能被一個應用程序所打開測試步驟:同整型原子變量的實驗步驟www.arm8.net 嵌入式論壇信號量:1.信號量對應的數據結構:struct semaphore2.信號量如何使用呢
    發表于 08-30 18:08

    Linux內核同步機制的自旋原理是什么?

    自旋是專為防止多處理器并發而引入的一種,它在內核中大量應用于中斷處理等部分(對于單處理器來說,防止中斷處理中的并發可簡單采用關閉中斷的方式,即在標志寄存器中關閉/打開中斷標志位,不需要自旋
    發表于 03-31 08:06

    怎么在atmega128中實現自旋

    什么是自旋?有哪些缺陷?怎么在atmega128中實現自旋
    發表于 01-24 06:54

    《深入理解C++11C++11新特性解析與應用的詳細電子教材免費下載

    國內首本全面深入解讀 C++11 新標準的專著,由 C++ 標準委員會代表和 IBM XL 編譯器中國開發團隊共同撰寫。不僅詳細闡述了 C++11 標準的設計原則,而且系統地講解了 C++11
    發表于 08-27 08:00 ?0次下載

    信號量和自旋

    。??? Linux 使用的同步機制可以說從2.0到2.6以來不斷發展完善。從最初的原子操作,到后來的信號量,從大內核到今天的自旋。這些同步機制的發展伴隨 Linux從單處理器到對稱多處理器的過度
    發表于 04-02 14:43 ?878次閱讀

    自旋的發展歷史與使用方法

    自旋是Linux內核里最常用的之一,自旋的概念很簡單,就是如果加鎖失敗在等時是使用休眠等
    的頭像 發表于 08-08 08:51 ?2052次閱讀

    使用Linux自旋實現互斥點燈

    自旋最多只能被一個可執行線程持有。如果一個線程試圖獲得一個已經被持有的自旋,那么該線程將循環等待,然后不斷的判斷是否能夠被成功獲取,直
    的頭像 發表于 04-13 15:09 ?948次閱讀
    使用Linux<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b><b class='flag-5'>實現</b>互斥點燈

    C++11新的類功能(特殊成員函數、override和final)

    C++11在原有的4個特殊成員函數(默認構造函數、復制構造函數、復制賦值運算符和析構函數)的基礎上新增了移動構造函數和移動賦值運算符。
    的頭像 發表于 07-18 16:02 ?680次閱讀

    自旋和互斥的區別有哪些

    自旋 自旋與互斥很相似,在訪問共享資源之前對自旋
    的頭像 發表于 07-21 11:19 ?9859次閱讀

    基于C++11的線程池實現

    C++11 加入了線程庫,從此告別了標準庫不支持并發的歷史。然而 c++ 對于多線程的支持還是比較低級,稍微高級一點的用法都需要自己去實現,譬如線程池、信號量等。 線程池(thread pool
    的頭像 發表于 11-13 15:29 ?974次閱讀

    互斥自旋的區別 自旋臨界區可以被中斷嗎?

    互斥自旋的區別 自旋臨界區可以被中斷嗎? 互斥
    的頭像 發表于 11-22 17:41 ?1124次閱讀

    自旋和互斥的使用場景是什么

    自旋和互斥是兩種常見的同步機制,它們在多線程編程中被廣泛使用。在本文中,我們將介紹自旋和互斥
    的頭像 發表于 07-10 10:05 ?1397次閱讀

    互斥自旋實現原理

    互斥自旋是操作系統中常用的同步機制,用于控制對共享資源的訪問,以避免多個線程或進程同時訪問同一資源,從而引發數據不一致或競爭條件等問題。 互斥(Mutex) 互斥
    的頭像 發表于 07-10 10:07 ?936次閱讀