一、多线程同步
在操作系统中,多线程编程是目前一种主流的编程方式。如果一个程序没有用到多线程编程,那么这个程序自然就显得不高大上了。在很多有名的程序中,都号称使用了单线程,其实所谓单线程也只是针对它个主要的程序来说,在一些其它的处理机制上,可能仍然使用的是多线程。比如有名的Redist库,Node.js库等。避免使用多线程,主要是避免出现死锁,或者说避免资源的竞争导致整个程序在某种程度上的效率的下降。
但是,多线程编程自然会导致整个编程模型的复杂度极剧提高,作为不断涌现的新语言,当然不会视而不见,最主要的一种解决方式就是推出了协程这个概念。虽然说协程从某种意义上讲确实是大幅降低了编程的复杂度和难度。可是它仍然无法避免协程间资源的竞争(这里单纯指有栈协程)。
换句话说,资源的竞争才是真正的复杂的根本,那么有没有一种方法,可以不进行资源竞争,直接各个线程对资源进行访问而不需要考虑锁的问题呢?这个从根本意义上来讲是不可能存在的。但是工程的上的方式是有办法解决的。比如下面的原子库,使用原子库,自然就会大幅减少锁的粒度(尽量达到同一时间只有一个线程操作),尽量实现一种无锁编程。更或者,把锁的性质推移到硬件总线上去,同样实现一种无锁编程。
不管锁在还是不在,至少在应用层编程是不存在了,这才是根本。
简单的,就是真理。
二、STL中原子操作
原子操作在不同的语言中,有不同的实现,在STL中提供了如下的相关操作(https://en.cppreference.com/w/cpp/atomic):
在c++11到c++20整个发展的过程中,原子库也有较大的变化,这些细节可以查询相关的文档或者资料,不过以现在的c++编程在业界的推广速度,这点倒没有什么可太大担心的。毕竟,好多公司都放弃了c++,使用c++的,很多连c++11都没使用。
在STL中内存控制模型有以下几种:
std::memory_order
C++ Atomic operations library
Defined in header <atomic>
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;
(since C++11)
(until C++20)
enum class memory_order : /*unspecified*/ {
relaxed, consume, acquire, release, acq_rel, seq_cst
};
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
其具体的解释见下面:
但是涉及原子操作相关的主要有:
1、 原子存储操作(store):memorey_order_relaxed、memory_order_release、memory_order_seq_cst。
2、 原子读取操作(load):memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst。
3、 RMW操作(read-modify-write)(如atomic_compare_exchange):memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst
三、源码分析
看一下原子库的相关源码:
template<>
struct atomic<_ITYPE>
: _ATOMIC_ITYPE
{ /* template specialization that manages
values of _ITYPE atomically */
using value_type = _ITYPE;
using difference_type = _ITYPE;
atomic() noexcept = default;
constexpr atomic(_ITYPE _Val) noexcept
: _ATOMIC_ITYPE{(_ATOMIC_UINT)_Val}
{ // construct from _Val, initialization is not atomic
}
_ITYPE operator=(_ITYPE _Val) volatile noexcept
{ // assign from _Val
return (_ATOMIC_ITYPE::operator=(_Val));
}
_ITYPE operator=(_ITYPE _Val) noexcept
{ // assign from _Val
return (_ATOMIC_ITYPE::operator=(_Val));
}
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
};
#endif /* _ATOMIC_HAS_NO_SPECIALIZATION */
#ifdef _ATOMIC_IS_ADDRESS_TYPE
// STRUCT TEMPLATE PARTIAL SPECIALIZATION atomic<_Ty *>
template<class _Ty>
struct atomic<_Ty *>
: _Atomic_address
{ // template that manages values of _Ty * atomically
using value_type = _Ty *;
using difference_type = ptrdiff_t;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
atomic() noexcept = default;
constexpr atomic(_Ty *_Right) noexcept
: _Atomic_address{(_ATOMIC_UINT)_Right}
{ // construct from _Right, initialization is not atomic
}
_Ty *operator=(_Ty *_Right) volatile noexcept
{ // assign from _Right
return (reinterpret_cast<_Ty *>(
_Atomic_address::operator=((void *)_Right)));
}
_Ty *operator=(_Ty *_Right) noexcept
{ // assign from _Right
return (reinterpret_cast<_Ty *>(
_Atomic_address::operator=((void *)_Right)));
}
void store(_Ty *_Value, memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // store _Value into *this
_Atomic_address::store((void *)_Value, _Order);
}
void store(_Ty *_Value, memory_order _Order = memory_order_seq_cst) noexcept
{ // store _Value into *this
_Atomic_address::store((void *)_Value, _Order);
}
_NODISCARD _Ty *load(memory_order _Order = memory_order_seq_cst) const volatile noexcept
{ // return value held in *this
return (reinterpret_cast<_Ty *>(_Atomic_address::load(_Order)));
}
_NODISCARD _Ty *load(memory_order _Order = memory_order_seq_cst) const noexcept
{ // return value held in *this
return (reinterpret_cast<_Ty *>(_Atomic_address::load(_Order)));
}
operator _Ty *() const volatile noexcept
{ // return value held in *this
return (load());
}
operator _Ty *() const noexcept
{ // return value held in *this
return (load());
}
_Ty *exchange(_Ty *_Value, memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // exchange value stored in *this with _Value
return (reinterpret_cast<_Ty *>(_Atomic_address::exchange(
(void *)_Value, _Order)));
}
_Ty *exchange(_Ty *_Value, memory_order _Order = memory_order_seq_cst) noexcept
{ // exchange value stored in *this with _Value
return (reinterpret_cast<_Ty *>(_Atomic_address::exchange(
(void *)_Value, _Order)));
}
bool compare_exchange_weak(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order1, memory_order _Order2) volatile noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_weak(
(void *&)_Exp, (void *)_Value, _Order1, _Order2));
}
bool compare_exchange_weak(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order1, memory_order _Order2) noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_weak(
(void *&)_Exp, (void *)_Value, _Order1, _Order2));
}
bool compare_exchange_weak(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_weak(
(void *&)_Exp, (void *)_Value, _Order));
}
bool compare_exchange_weak(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order = memory_order_seq_cst) noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_weak(
(void *&)_Exp, (void *)_Value, _Order));
}
bool compare_exchange_strong(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order1, memory_order _Order2) volatile noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_strong(
(void *&)_Exp, (void *)_Value, _Order1, _Order2));
}
bool compare_exchange_strong(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order1, memory_order _Order2) noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_strong(
(void *&)_Exp, (void *)_Value, _Order1, _Order2));
}
bool compare_exchange_strong(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_strong(
(void *&)_Exp, (void *)_Value, _Order));
}
bool compare_exchange_strong(
_Ty *& _Exp, _Ty *_Value,
memory_order _Order = memory_order_seq_cst) noexcept
{ // compare and exchange value stored in *this with *_Exp, _Value
return (_Atomic_address::compare_exchange_strong(
(void *&)_Exp, (void *)_Value, _Order));
}
_Ty *fetch_add(ptrdiff_t _Value, memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // add _Value to value stored in *this
return (reinterpret_cast<_Ty *>(
_Atomic_address::fetch_add(_Value * sizeof (_Ty), _Order)));
}
_Ty *fetch_add(ptrdiff_t _Value, memory_order _Order = memory_order_seq_cst) noexcept
{ // add _Value to value stored in *this
return (reinterpret_cast<_Ty *>(
_Atomic_address::fetch_add(_Value * sizeof (_Ty), _Order)));
}
_Ty *fetch_sub(ptrdiff_t _Value, memory_order _Order = memory_order_seq_cst) volatile noexcept
{ // subtract _Value from value stored in *this
return (reinterpret_cast<_Ty *>(
_Atomic_address::fetch_sub(_Value * sizeof (_Ty), _Order)));
}
_Ty *fetch_sub(ptrdiff_t _Value, memory_order _Order = memory_order_seq_cst) noexcept
{ // subtract _Value from value stored in *this
return (reinterpret_cast<_Ty *>(
_Atomic_address::fetch_sub(_Value * sizeof (_Ty), _Order)));
}
_Ty *operator++(int) volatile noexcept
{ // increment stored pointer
return (fetch_add(1));
}
_Ty *operator++(int) noexcept
{ // increment stored pointer
return (fetch_add(1));
}
_Ty *operator--(int) volatile noexcept
{ // decrement stored pointer
return (fetch_sub(1));
}
_Ty *operator--(int) noexcept
{ // decrement stored pointer
return (fetch_sub(1));
}
_Ty *operator+=(ptrdiff_t _Right) volatile noexcept
{ // add _Right to value stored in *this
return (fetch_add(_Right) + _Right);
}
_Ty *operator+=(ptrdiff_t _Right) noexcept
{ // add _Right to value stored in *this
return (fetch_add(_Right) + _Right);
}
_Ty *operator-=(ptrdiff_t _Right) volatile noexcept
{ // subtract _Right from value stored in *this
return (fetch_sub(_Right) - _Right);
}
_Ty *operator-=(ptrdiff_t _Right) noexcept
{ // subtract _Right from value stored in *this
return (fetch_sub(_Right) - _Right);
}
_Ty *operator++() volatile noexcept
{ // increment stored pointer
return (*this += 1);
}
_Ty *operator++() noexcept
{ // increment stored pointer
return (*this += 1);
}
_Ty *operator--() volatile noexcept
{ // decrement stored pointer
return (*this -= 1);
}
_Ty *operator--() noexcept
{ // decrement stored pointer
return (*this -= 1);
}
};
具体调用的相关函数,可以继续在相关头文件里查看,这个其实没啥,代码还是比较清晰简单的。里面需要说明一下的是compare_exchange_strong(CAS),这也是其它语言实现无锁编程的方式,基本都类似。
四、实例
看一下自定义的智能指针的一个实例及相关应用的例程(cppreference.com):
#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
std::atomic<bool> lock(false); // holds true when locked
// holds false when unlocked
void f(int n)
{
for (int cnt = 0; cnt < 100; ++cnt) {
while(std::atomic_exchange_explicit(&lock, true, std::memory_order_acquire))
; // spin until acquired
std::cout << "Output from thread " << n << '\n';
std::atomic_store_explicit(&lock, false, std::memory_order_release);
}
}
int main()
{
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f, n);
}
for (auto& t : v) {
t.join();
}
}
输出结果:
Output from thread 2
Output from thread 6
Output from thread 7
...<exactly 1000 lines>...
在cppreference.com上相关的例程主要在相关的应用函数使用上,如std::atomic_flag_test_and_set, std::atomic_flag_test_and_set_explicit,包括std::memory_order,而直接的介绍上则很少有例程,这个简单说明一下。
五、总结
一般来说,++i和i++都不认为是原子操作,特别是在多核计算机普遍流行的今天,更是如此。原子操作是如此之美,是不是可以把锁替代掉呢?不要想得太简单,这只是给你提供了一个更多的选择的机会,而不是要让你把哪个干掉。不过总体仍然是表明,只要朝着简单易用的方向前进的东西,基本都是正确的。
大繁至简,亦如是!