目录
Operations supported by certain specializations (integral and/or pointer)
std::atomic::compare_exchange_weak
Wait-free ring buffer - 无锁环形队列
Lock-free multi-producer queue- 无锁多生产者队列
WHAT
ABSTRACT
Objects of atomic types contain a value of a particular type (T).
The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races (i.e., doing that is well-defined behavior, with accesses properly sequenced). Generally, for all other objects, the possibility of causing a data race for accessing the same object concurrently qualifies the operation as undefined behavior.
Additionally, atomic objects have the ability to synchronize access to other non-atomic objects in their threads by specifying different memory orders.
原子类型的对象包含一个特定类型(T)的值。
原子对象的主要特征是,从不同的线程访问这个包含的值不会导致数据竞争(也就是说,这样做是定义良好的行为,访问顺序正确)。一般来说,对于所有其他对象,由于并发访问同一对象而导致数据竞争的可能性,因此将该操作限定为未定义的行为。
此外,通过指定不同的内存顺序,原子对象能够同步访问其线程中的其他非原子对象。
Template parameters
T
Type of the contained value.
This shall be a trivially copyable type.
包含值的类型。 这应该是一个可简单复制的类型。
Member Functions
General atomic operations
(constructor) | Construct atomic (public member function ) 构造函数 |
operator= | Assign contained value (public member function ) 传递包含的值(公共成员函数) |
is_lock_free | Is lock-free (public member function ) 判断在 *this 的基本操作是否存在任意的锁。(公共成员函数) |
store | Modify contained value (public member function ) 设置*this 的值(公共成员函数) |
load | Read contained value (public member function ) 获取*this 的值(公共成员函数) |
operator T | Access contained value (public member function ) 读取并返回该存储的值(公共成员函数) |
exchange | Access and modify contained value (public member function ) 访问和修改所包含的值(公共成员函数) |
compare_exchange_weak | Compare and exchange contained value (weak) (public member function ) 比较并改变包含的值(公共成员函数) 比较原子对象所包含值的内容与预期值: -如果为真,它会用val替换包含的值(像store一样)。 -如果为false,则用包含的值替换expected。 这个函数可能在满足真的情况下仍然返回false,所以可以在循环里使用 |
compare_exchange_strong | Compare and exchange contained value (strong) (public member function ) 比较并改变包含的值(公共成员函数) 比较原子对象所包含值的内容与预期值: -如果为真,它会用val替换包含的值(像store一样)。 -如果为false,则用包含的值替换expected。 |
Operations supported by certain specializations (integral and/or pointer)
Do not use floating point types here
fetch_add | Add to contained value (public member function ) |
fetch_sub | Subtract from contained value (public member function ) |
fetch_and | Apply bitwise AND to contained value (public member function ) |
fetch_or | Apply bitwise OR to contained value (public member function ) |
fetch_xor | Apply bitwise XOR to contained value (public member function ) |
operator++ | Increment container value (public member function ) |
operator-- | Decrement container value (public member function ) |
HOW
std::atomic::store & load
prototype
void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; void store (T val, memory_order sync = memory_order_seq_cst) noexcept;
abstract
Replaces the contained value with val.
The operation is atomic and follows the memory ordering specified by sync.
parameters
val
Value to copy to the contained object. T is atomic's template parameter (the type of the contained value).
sync
Synchronization mode for the operation. This shall be one of these possible values of the enum type memory_order:
There are six types of memory_orders defined by the C++ standard library, of which memory_order_acq_rel can be regarded as a combination of memory_order_acquire and memory_order_release.
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
demo
// atomic::load/store example
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::memory_order_relaxed
#include <thread> // std::thread
std::atomic<int> foo (0);
void set_foo(int x) {
foo.store(x,std::memory_order_relaxed); // set value atomically
}
void print_foo() {
int x;
do {
x = foo.load(std::memory_order_relaxed); // get value atomically
} while (x==0);
std::cout << "foo: " << x << '\n';
}
int main ()
{
std::thread first (print_foo);
std::thread second (set_foo,10);
first.join();
second.join();
return 0;
}
std::atomic::exchange
prototype
T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;
abstract
Replaces the contained value by val and returns the value it had immediately before.
The entire operation is atomic (an atomic read-modify-write operation): the value is not affected by other threads between the instant its value is read (to be returned) and the moment it is modified by this function.
将包含的值替换为val并返回它之前的值。
整个操作是原子(原子的读-修改-写操作):价值不受其他线程之间的即时影响它的值是读取(返回),现在是修改这个函数。
return value
The contained value before the call. T is atomic's template parameter (the type of the contained value).
demo
// atomic::exchange example
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread
#include <vector> // std::vector
std::atomic<bool> ready (false);
std::atomic<bool> winner (false);
void count1m (int id) {
while (!ready) {} // wait for the ready signal
for (int i=0; i<1000000; ++i) {} // go!, count to 1 million
if (!winner.exchange(true)) { std::cout << "thread #" << id << " won!\n"; }
};
int main ()
{
std::vector<std::thread> threads;
std::cout << "spawning 10 threads that count to 1 million...\n";
for (int i=1; i<=10; ++i) threads.push_back(std::thread(count1m,i));
ready = true;
for (auto& th : threads) th.join();
return 0;
}
std::atomic::compare_exchange_weak
prototype
bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) volatile noexcept; bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) noexcept; bool compare_exchange_weak (T& expected, T val, memory_order success, memory_order failure) volatile noexcept; bool compare_exchange_weak (T& expected, T val, memory_order success, memory_order failure) noexcept;
abstract
Compares the contents of the atomic object's contained value with expected:
-
if true, it replaces the contained value with val (like store).
-
if false, it replaces expected with the contained value .
The function always accesses the contained value to read it, and -if the comparison is true- it then also replaces it. But the entire operation is atomic: the value cannot be modified by other threads between the instant its value is read and the moment it is replaced.
比较原子对象所包含值的内容与预期值:
-如果为真,它会用val替换包含的值(像store一样)。
-如果为false,则用包含的值替换expected。
该函数总是访问所包含的值来读取它,如果比较为真,那么它也会替换它。
这个函数可能在满足真的情况下仍然返回false,所以可以在循环里使用.
return value
true if expected compares equal to the contained value (and does not fail spuriously). false otherwise.
如果预期的值与所包含的值相等(没有因为代码错误而导致失败),则为True。
否则错误。
demo
// atomic::compare_exchange_weak example:
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread
#include <vector> // std::vector
// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);
void append (int val) { // append an element to the list
Node* oldHead = list_head;
Node* newNode = new Node {val,oldHead};
// what follows is equivalent to: list_head = newNode, but in a thread-safe way:
while (!list_head.compare_exchange_weak(oldHead,newNode))
newNode->next = oldHead;
}
int main ()
{
// spawn 10 threads to fill the linked list:
std::vector<std::thread> threads;
for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
for (auto& th : threads) th.join();
// print contents:
for (Node* it = list_head; it!=nullptr; it=it->next)
std::cout << ' ' << it->value;
std::cout << '\n';
// cleanup:
Node* it; while (it=list_head) {list_head=it->next; delete it;}
return 0;
}
ASSISTANCE
https://cplusplus.com/reference/atomic/atomic/
https://www.boost.org/doc/libs/1_75_0/doc/html/atomic.html#atomic.introduction
TEMPLATE
Reference counting - 引用计数
abstract
The purpose of a reference counter is to count the number of pointers to an object. The object can be destroyed as soon as the reference counter reaches zero.
Implementation
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
Usage
X::pointer x = new X;
Spinlock - 自旋锁
abstract
The purpose of a spin lock is to prevent multiple threads from concurrently accessing a shared data structure. In contrast to a mutex, threads will busy-wait and waste CPU cycles instead of yielding the CPU to another thread. Do not use spinlocks unless you are certain that you understand the consequences.
旋转锁的目的是防止多个线程并发地访问共享数据结构。与互斥锁相反,线程将忙等待并浪费CPU周期,而不是将CPU让给另一个线程。不要使用自旋锁,除非你确定你明白其后果,可能的使用场景是:在现有系统中的锁操作是短时锁的情况下,要求线程强制一定顺序去执行。
Implementation
#include <boost/atomic.hpp>
class spinlock {
private:
typedef enum {Locked, Unlocked} LockState;
boost::atomic<LockState> state_;
public:
spinlock() : state_(Unlocked) {}
void lock()
{
while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) {
/* busy-wait */
}
}
void unlock()
{
state_.store(Unlocked, boost::memory_order_release);
}
};
Usage
spinlock s;
s.lock();
// access data structure here
s.unlock();
Wait-free ring buffer - 无锁环形队列
abstract
A wait-free ring buffer provides a mechanism for relaying objects from one single "producer" thread to one single "consumer" thread without any locks. The operations on this data structure are "wait-free" which means that each operation finishes within a constant number of steps. This makes this data structure suitable for use in hard real-time systems or for communication with interrupt/signal handlers.
CAS无锁循环队列提供了一种机制,可以在没有任何锁的情况下将对象从一个“生产者”线程中继到一个“消费者”线程。该数据结构上的操作是“无等待”的,这意味着每个操作在固定数量的步骤内完成。这使得该数据结构适用于硬实时系统或与中断/信号处理程序通信。
Implementation
#include <boost/atomic.hpp>
template<typename T, size_t Size>
class ringbuffer {
public:
ringbuffer() : head_(0), tail_(0) {}
bool push(const T & value)
{
size_t head = head_.load(boost::memory_order_relaxed);
size_t next_head = next(head);
if (next_head == tail_.load(boost::memory_order_acquire))
return false;
ring_[head] = value;
head_.store(next_head, boost::memory_order_release);
return true;
}
bool pop(T & value)
{
size_t tail = tail_.load(boost::memory_order_relaxed);
if (tail == head_.load(boost::memory_order_acquire))
return false;
value = ring_[tail];
tail_.store(next(tail), boost::memory_order_release);
return true;
}
private:
size_t next(size_t current)
{
return (current + 1) % Size;
}
T ring_[Size];
boost::atomic<size_t> head_, tail_;
};
Usage
ringbuffer<int, 32> r;
// try to insert an element
if (r.push(42)) { /* succeeded */ }
else { /* buffer full */ }
// try to retrieve an element
int value;
if (r.pop(value)) { /* succeeded */ }
else { /* buffer empty */ }
Lock-free multi-producer queue- 无锁多生产者队列
abstract
The purpose of the lock-free multi-producer queue is to allow an arbitrary number of producers to enqueue objects which are retrieved and processed in FIFO order by a single consumer.
无锁多生产者队列的目的是允许任意数量的生产者排队对象,这些对象由单个消费者按照FIFO顺序检索和处理。
Implementation
template<typename T>
class lockfree_queue {
public:
struct node {
T data;
node * next;
};
void push(const T &data)
{
node * n = new node;
n->data = data;
node * stale_head = head_.load(boost::memory_order_relaxed);
do {
n->next = stale_head;
} while (!head_.compare_exchange_weak(stale_head, n, boost::memory_order_release));
}
node * pop_all(void)
{
T * last = pop_all_reverse(), * first = 0;
while(last) {
T * tmp = last;
last = last->next;
tmp->next = first;
first = tmp;
}
return first;
}
lockfree_queue() : head_(0) {}
// alternative interface if ordering is of no importance
node * pop_all_reverse(void)
{
return head_.exchange(0, boost::memory_order_consume);
}
private:
boost::atomic<node *> head_;
};
Usage
lockfree_queue<int> q;
// insert elements
q.push(42);
q.push(2);
// pop elements
lockfree_queue<int>::node * x = q.pop_all()
while(x) {
X * tmp = x;
x = x->next;
// process tmp->data, probably delete it afterwards
delete tmp;
}