Jua*_*nto 3 c++ atomic lock-free memory-barriers stdatomic
我有一个小类,它使用std :: atomic进行无锁操作。由于该课程被广泛调用,因此影响了性能,并且遇到了麻烦。
该类类似于LIFO,但是一旦调用pop()函数,它仅返回其环形缓冲区的最后写入元素(仅当自上次pop()之后存在新元素时)。
一个线程正在调用push(),另一个线程正在调用pop()。
由于这占用了我的计算机时间太多,因此我决定进一步研究std :: atomic类及其memory_order。我已经阅读了很多StackOverflow以及其他来源和书籍中的memory_order帖子,但是我无法对不同的模式有一个清晰的了解。特别是,我在获取和释放模式之间挣扎:我也看不出为什么它们与memory_order_seq_cst不同。
memory_order_relaxed:在同一线程中,原子操作是即时的,但是其他线程可能无法立即看到最新的值,它们将需要一些时间才能被更新。编译器或OS可以自由地对代码进行重新排序。
memory_order_acquire / release:由atomic :: load使用。它防止重新排序之前存在的代码行(编译器/ OS可能在此行之后对其重新排序),并使用此线程或另一个线程中的memory_order_release或memory_order_seq_cst读取存储在此原子上的最新值。memory_order_release还可以防止对该代码重新排序之后的代码。因此,在获取/发布中,两者之间的所有代码都可以被OS改组。我不确定这是在同一线程还是不同线程之间。
memory_order_seq_cst:最容易使用,因为就像我们使用变量的自然写法一样,立即刷新其他线程加载函数的值。
template<typename T>
class LockFreeEx
{
public:
void push(const T& element)
{
const int wPos = m_position.load(std::memory_order_seq_cst);
const int nextPos = getNextPos(wPos);
m_buffer[nextPos] = element;
m_position.store(nextPos, std::memory_order_seq_cst);
}
const bool pop(T& returnedElement)
{
const int wPos = m_position.exchange(-1, std::memory_order_seq_cst);
if (wPos != -1)
{
returnedElement = m_buffer[wPos];
return true;
}
else
{
return false;
}
}
private:
static constexpr int maxElements = 8;
static constexpr int getNextPos(int pos) noexcept {return (++pos == maxElements)? 0 : pos;}
std::array<T, maxElements> m_buffer;
std::atomic<int> m_position {-1};
};
Run Code Online (Sandbox Code Playgroud)
因此,我的第一个想法是在所有原子操作中使用memory_order_relaxed,因为pop()线程处于循环中,每10-15毫秒查找一次pop函数中的可用更新,然后允许它在第一个pop()函数中失败以实现后来有一个新的更新。仅一毫秒。
另一种选择是使用发布/获取-但我不确定它们。在所有 store()中使用release,并在所有 load()函数中获取。
不幸的是,我描述的所有memory_order似乎都可以工作,并且我不确定它们是否会失败(如果应该失败)。
请,你能告诉我在这里使用宽松的内存顺序是否遇到问题?还是我应该使用发布/获取(也许对它们的进一步解释可能对我有帮助)?为什么?
我认为对于所有此类的store()或load()而言,放松是此类的最佳选择。但是我不确定!
谢谢阅读。
由于我看到每个人都在要求'char',因此我将其更改为int,问题已解决!但这不是我要解决的问题。
正如我之前所说,该类对于LIFO来说很可能,但是只有在最后一个元素(如果有)才重要的地方。
我有一个大的结构T(可复制和可赋值),我必须以无锁方式在两个线程之间共享。因此,我唯一知道的方法是使用一个循环缓冲区,该缓冲区写入T的最后一个已知值,以及一个原子,该原子知道写入的最后一个值的索引。如果没有,索引将为-1。
请注意,我的推线程必须知道何时有可用的“新T”,这就是pop()返回布尔值的原因。
再次感谢大家尝试协助我完成记忆订单!:)
template<typename T>
class LockFreeEx
{
public:
LockFreeEx() {}
LockFreeEx(const T& initValue): m_data(initValue) {}
// WRITE THREAD - CAN BE SLOW, WILL BE CALLED EACH 500-800ms
void publish(const T& element)
{
// I used acquire instead relaxed to makesure wPos is always the lastest w_writePos value, and nextPos calculates the right one
const int wPos = m_writePos.load(std::memory_order_acquire);
const int nextPos = (wPos + 1) % bufferMaxSize;
m_buffer[nextPos] = element;
m_writePos.store(nextPos, std::memory_order_release);
}
// READ THREAD - NEED TO BE VERY FAST - CALLED ONCE AT THE BEGGINING OF THE LOOP each 2ms
inline void update()
{
// should I change to relaxed? It doesn't matter I don't get the new value or the old one, since I will call this function again very soon, and again, and again...
const int writeIndex = m_writePos.load(std::memory_order_acquire);
// Updating only in case there is something new... T may be a heavy struct
if (m_readPos != writeIndex)
{
m_readPos = writeIndex;
m_data = m_buffer[m_readPos];
}
}
// NEED TO BE LIGHTNING FAST, CALLED MULTIPLE TIMES IN THE READ THREAD
inline const T& get() const noexcept {return m_data;}
private:
// Buffer
static constexpr int bufferMaxSize = 4;
std::array<T, bufferMaxSize> m_buffer;
std::atomic<int> m_writePos {0};
int m_readPos = 0;
// Data
T m_data;
};
Run Code Online (Sandbox Code Playgroud)
内存顺序与您看到原子对象的某些特定更改无关,而与该更改可以保证周围的代码有关。弛豫的原子除了对原子对象本身的更改以外,不能保证其他任何事情:更改将是原子的。但是您不能在任何同步上下文中使用宽松的原子。
并且您有一些需要同步的代码。您想弹出已推送的内容,而不想弹出尚未推送的内容。因此,如果您使用轻松的操作,则不能保证您的弹出窗口会看到此推送代码:
m_buffer[nextPos] = element;
m_position.store(nextPos, std::memory_relaxed);
Run Code Online (Sandbox Code Playgroud)
如所写。同样可以这样看:
m_position.store(nextPos, std::memory_relaxed);
m_buffer[nextPos] = element;
Run Code Online (Sandbox Code Playgroud)
因此,您可能尝试从尚不存在的缓冲区中获取元素。因此,您必须使用一些同步,并且至少使用获取/释放内存顺序。
以及您的实际代码。我认为顺序可以如下:
const char wPos = m_position.load(std::memory_order_relaxed);
...
m_position.store(nextPos, std::memory_order_release);
...
const char wPos = m_position.exchange(-1, memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)