它不是編譯器和操作系統特定的,它是特定於體系結構的。編譯器和操作系統進入它,因爲它們是你工作的工具,但它們不是那些設定真正規則的工具。這就是C++標準不會觸及問題的原因。
我從來沒有聽說過一個64位的整數寫入,它可以分成兩個32位的寫入,中途被中斷。 (是的,這是邀請其他人發佈反例。)具體來說,我從來沒有聽說過CPU的加載/存儲單元允許中斷未對齊的寫入;一箇中斷源必須等待整個錯位訪問完成。
要有一個可中斷的加載/存儲單元,其狀態必須保存到堆棧中,並且加載/存儲單元將剩餘的CPU狀態保存到堆棧中。這將是巨大的複雜,並且容易出錯,如果加載/存儲單元是可中斷的......並且您將獲得的所有內容是一個週期響應中斷的更少的等待時間,其充其量是以十的週期。完全不值得。
早在1997年,一位同事和我寫了一個C++ Queue模板,用於多處理系統。 (每個處理器都有自己的操作系統運行,以及它自己的本地內存,所以這些隊列只用於處理器之間共享的內存。)我們設計了一種通過單個整數寫入來改變隊列狀態的方法,並將此寫入爲原子操作。此外,我們要求隊列的每一端(即讀或寫索引)都由一個且只有一個處理器擁有。十三年後,代碼仍然運行良好,我們甚至有一個處理多個閱讀器的版本。
但是,如果您想將64位整數寫入原子,請將該字段與64位邊界對齊。爲什麼要擔心?
編輯:對於你在你的評論中提到的情況,我需要更多的信息來確定,所以讓我舉一個例子,可以實現沒有專門的同步代碼的東西。
假設你有N個作者和一個閱讀器。你希望作家能夠向讀者發信號。事件本身沒有數據;你只是想要一個事件計數,真的。
聲明共享內存結構,所有的作家和讀者之間共享:(使此一類或模板或您認爲合適的任何)
#include <stdint.h>
struct FlagTable
{ uint32_t flag[NWriters];
};
每一個作家需要被告知其索引並給予一個指向該表:
class Writer
{public:
Writer(FlagTable* flags_, size_t index_): flags(flags_), index(index_) {}
void SignalEvent(uint32_t eventCount = 1);
private:
FlagTable* flags;
size_t index;
}
當筆者想要信號的事件(或幾個),更新其標誌:
void Writer::SignalEvent(uint32_t eventCount)
{ // Effectively atomic: only one writer modifies this value, and
// the state changes when the incremented value is written out.
flags->flag[index] += eventCount;
}
讀者保持它已經看到的所有標誌值的本地副本:
class Reader
{public:
Reader(FlagTable* flags_): flags(flags_)
{ for(size_t i = 0; i < NWriters; ++i)
seenFlags[i] = flags->flag[i];
}
bool AnyEvents(void);
uint32_t CountEvents(int writerIndex);
private:
FlagTable* flags;
uint32_t seenFlags[NWriters];
}
要找出是否有任何事件發生,它只是看起來的變化值:
bool Reader::AnyEvents(void)
{ for(size_t i = 0; i < NWriters; ++i)
if(seenFlags[i] != flags->flag[i])
return true;
return false;
}
如果發生了什麼事情,我們可以檢查每個來源並獲得事件數:
uint32_t Reader::CountEvents(int writerIndex)
{ // Only read a flag once per function call. If you read it twice,
// it may change between reads and then funny stuff happens.
uint32_t newFlag = flags->flag[i];
// Our local copy, though, we can mess with all we want since there
// is only one reader.
uint32_t oldFlag = seenFlags[i];
// Next line atomically changes Reader state, marking the events as counted.
seenFlags[i] = newFlag;
return newFlag - oldFlag;
}
現在這一切都陷入了困境?它是非阻塞的,也就是說,在Writer寫入內容之前,您無法讓Reader進入睡眠狀態。讀者必須選擇坐在等待AnyEvents()
的旋轉循環中以返回true
,這可以最大限度地減少延遲,或者每次都可以睡一會兒,這樣可以節省CPU但可以讓很多事件累積起來。所以總比沒有好,但這不是一切的解決方案。
使用實際的同步原語,人們只需要用一個互斥和條件變量來包裝這個代碼,使它正確地阻塞:讀者會睡覺,直到有事情要做。由於您對標誌使用了原子操作,因此實際上可以將互斥鎖的鎖定時間保持在最小值:Writer只需將互斥鎖鎖定足夠長的時間以發送條件,而不設置標誌,讀取器只需要在調用AnyEvents()
之前等待條件(基本上,它就像上面的睡眠環路情況,但是具有等待條件而不是睡眠呼叫)。
恕我直言,原子操作是可能的,http://stackoverflow.com/questions/930897/c-atomic-operations-for-lock-free - 結構可能會有所幫助。 – 2010-04-28 13:02:04