2016-03-03 47 views
1

...還是我必須寫我自己的? (順便說一句,我在工作c)在通用C庫中是否有TestAndSet(volatile int * lock)函數?

我在寫類似於維基百科什麼是一個實現:

volatile int lock = 0; 

void Critical() { 
    while (TestAndSet(&lock) == 1); 
    critical section // only one process can be in this section at a time 
    lock = 0 // release lock when finished with the critical section 
} 

但我似乎無法找到一個預置的TestAndSet(volatile int *lock)。他們可能是什麼樣子

例子包括:

#define LOCKED 1 
int TestAndSet(volatile int* lockPtr) { 
    int oldValue; 
    oldValue = *lockPtr; 
    *lockPtr = LOCKED; 
    return oldValue; 
} 

理想情況下,我想的東西,可以在Linux和Windows工作。同樣,我讀過原子指令的執行是依賴於硬件的。我不確定這是否會影響事物,或者如何判斷硬件是否支持它,然後運行替代方案。

謝謝!

額外的上下文信息: 我問這個問題的原因是一組用於訪問數據結構功能的發展(如添加()取()刪除()等...)幾個線程正在訪問它進行修改和實時顯示某些元素。

互斥體: 因爲關鍵區域不是整個散列表,而是特定的成員被給定的函數訪問,所以我投反對互斥體(糾正我,如果我的理由是沒有根據的)。因此使用互斥鎖會導致整個數據結構的性能瓶頸。

替代: 是什麼讓我看到TestAndSet()是因爲在數據結構中的每個元素上放置一個「beingAccessed」標誌更有意義。這個標誌將被一個想要訪問它的函數檢查,如果它是假的,那麼它將被設置爲true,然後該函數將做它必須做的事情,然後釋放那個元素而不凍結整個結構。

評論@ M.M: @chux和您都提到的原因,示例實現不正確。 對於忙碌的等待,我的理解是,它在較低的級別上用於開發更高級別的同步機制。請參閱我的編輯上面的re:mutexes。 volatile不是爲了確保原子性,而是爲了確保每次訪問它時由原子函數檢查它的值,因爲多個線程可以隨時修改該變量。 我想象/希望的原子性是由作用於所討論的變量的函數提供的。 問題具體到你寫的東西:你的代碼說:「注意:不要使用」volatile「」但你提供的標準函數原型是易失性的,所以非易失性標誌變量在原子函數中被轉換爲volatile?謝謝。

+0

N.B.我添加了「易失性」部分。這不是我找到的原始例子。 –

+1

'oldValue = * lockPtr;'和'* lockPtr = LOCKED;'保持'lockPtr'不變? – chux

+0

@chux - 我知道對不對?這就是爲什麼我不確定是否丟失了某些東西,或者有關於我不知道的指針的屬性 –

回答

2

在C11標準,您的標誌看起來像:

#include <stdatomic.h> 

// Lock-free atomic flag, initially unset. Note: do not use "volatile" 
atomic_flag lock = ATOMIC_FLAG_INIT; 

,並有一個標準功能:

_Bool atomic_flag_test_and_set(volatile atomic_flag *object); 

執行您需要的操作。

請注意,您的示例實現無效,因爲該標誌可能會被另一個線程在lock_ptr的讀寫之間更改。我使用clang 3.7和gcc 5.x進行了測試,並且他們實現了atomic_flag_test_and_set作爲XCHG彙編指令。


此外,您Critical功能實現了「自旋鎖」,即它會最大程度的發揮CPU在試圖改變標誌不斷敲打了。這只是一個很好的主意,因爲只有少數CPU週期才能執行鎖定。有關此主題的進一步閱讀,請參閱this thread

對於受關鍵部分保護的代碼可能進行系統調用的正常使用,最好使用操作系統工具對該標誌執行非繁忙等待。

C11線程庫包含一個互斥鎖。As of 2015,顯然沒有主要的編譯器/庫組合本地支持C11線程,儘管在該線程上回答有一個github項目,它通過POSIX線程實現C11線程。

當然,您可以使用歷史悠久的#if技術在Windows中選擇CRITICAL_SECTION,並在所有其他操作系統中選擇pthread庫互斥體。


關於使用volatile:這對多線程既不必要也不足夠。

這不是足夠的因爲它不能保證原子性。一個易失性對象仍然可以被多個線程(數據競爭,UB)同時讀取/寫入。您必須使用原子對象,或者使用C11 Atomics或編譯器特定的原子特徵。

而不是必要因爲自C11以來,語言規範包括內存排序規則,該規則阻止編譯器對原子的重新排序操作。您可以閱讀有關C11標準(或N1570)中支持的各種內存排序模型的更多信息。因爲C99標準沒有任何內存圍欄的概念,所以在C99中推薦使用volatile sig_atomic_t作爲一種攻擊手段。然而,向前邁進,既然實際的原子是可用的,那麼易變的黑客應該寄存在垃圾堆中。

+0

關於'volatile'的不足的好解釋。 – chux

+0

@ M.M - 我的評論沒有足夠的空間,所以我把我的評論放在了我的問題的第二個編輯 –

+0

你和@Collin Dauphinee都是非常有幫助的。我現在堅持要點,那就是我的atomic_flag是一個結構體定義的一部分,並且在那裏有ATOMIC_FLAG_INIT,它似乎是在它後面的結構中覆蓋數據。我認爲這可能與填充的對齊/必要性有關,但我不知道如何去做。有任何想法嗎? –

-1

在Linux(和一般所有的* nix-ES),可以使用並行線程:

#include <pthread.h> 
pthread_mutex_t mutex; 
void init_mutex() { 
    pthread_mutex_init(&mutex, NULL); 
} 
void lock_mutex() { 
    pthread_mutex_lock(&mutex); 
} 
void release_mutex() { 
    pthread_mutex_unlock(&mutex); 
} 
void delete_mutex() { 
    pthread_mutex_destroy(&mutex); 
} 

你可以關閉過程中得到一個指向mutex,並轉換爲int*,但它是無感!這四個功能做:

  • init_mutex - 呼叫在程序啓動,初始化資源甚至可稱爲前main(前「void」關鍵字與__attribute__((constructor))標記它
  • delete_mutex - 呼籲程序完成,釋放資源。
  • lock_mutex - 會鎖定您的鎖定,不允許其他線程訪問此功能(有沒有辦法來限制它的進程之間)
  • release_mutex - 調用它讓運行時知道關鍵部分已經結束,並且已準備好接受下一個線程。

這些函數將構建等待互斥體的線程隊列。看到這個例子(六線程):

A -> B -> C -> D -> E -> [MUTEX] -> F (critical) -> [MUTEX END] 
[F] release_mutex 
A -> B -> C -> D -> [MUTEX] -> E (critical) -> [MUTEX END] -> F 

我不知道在Windows上做到這一點。如果你想到Linux和Windows通用的東西,那就不好。如果你想到硬件,我不得不說NO!線程由內核管理(有時由C庫管理)。

+0

感謝您的回覆。我在我的問題中添加了其他信息,解釋爲什麼我決定不使用互斥鎖。 –

0

這通常使用內聯彙編語言或編譯器擴展來完成。例如,您可以在Linux和Windows中使用GCC Atomic Functions。 Microsoft C編譯器可能具有相同的功能。

3

C11包含提供此功能的新的atomic library。請參閱atomic_flag_test_and_set函數;只是一個atomic_flag*

您可能希望只使用,而不是滾動您自己的同步在C11的threads.h提供的mutexmtx_*)功能,取代你int*

+0

現在探索此... –

+0

謝謝你的幫助。請在@M.M的回答下查看我的評論以瞭解後續問題。你們都有正確的答案,但他提供的附加代碼示例非常有用。我感謝你抽出時間。再次感謝。 –

相關問題