
在上一篇mutex的学习中我们知道在多线程环境下对一个变量同时进很读写必须要加锁才能避免数据竞争。如果只是对一个简单类型的变量进行加锁解锁感觉是不是有点冗余其实可以通过atomic类型达到同样的效果那么是atomic原子类型呢原子类型的对象包含一个特定类型T的值。原子对象的主要特点是从不同线程访问该内部值时不会引发数据竞争即只要访问顺序正确则这种行为是明确定义的。通常情况下对于所有其他对象若在并发访问同一对象时可能引发数据竞争则该操作被视为未定义行为。此外原子对象还能够通过指定不同的内存顺序在其线程中同步访问其他非原子对象。本篇记录atomic原子对象的学习。以下是atomic常函数成员函数(构造函数)构造原子对象(公开成员函数)operator存储值于原子对象(公开成员函数)is_lock_free检查原子对象是否免锁(公开成员函数)store原子地以非原子实参替换原子对象的值(公开成员函数)load原子地获得原子对象的值(公开成员函数)operator T从原子对象加载值(公开成员函数)exchange原子地替换原子对象的值并获得它先前持有的值(公开成员函数)compare_exchange_weakcompare_exchange_strong原子地比较原子对象与非原子实参的值相等时进行原子交换不相等时进行原子加载(公开成员函数)wait(C20)阻塞线程直到被提醒且原子值更改(公开成员函数)notify_one(C20)提醒至少一个在原子对象上的等待中阻塞的线程(公开成员函数)notify_all(C20)提醒所有在原子对象上的等待中阻塞的线程(公开成员函数)常量is_always_lock_free[静态] (C17)指示该类型是否始终免锁(公开静态成员常量)特化成员函数为整数、浮点数(C20 起)和指针类型特化fetch_add原子地将实参加到存储于原子对象的值上并返回先前保有的值(公开成员函数)fetch_sub原子地从存储于原子对象的值减去实参并获得先前保有的值(公开成员函数)operatoroperator-与原子值进行加、减(公开成员函数)仅为整数和指针类型特化fetch_max(C26)原子地进行实参和原子对象的值的 std::max并获得先前保有的值(公开成员函数)fetch_min(C26)原子地进行实参和原子对象的值的 std::min并获得先前保有的值(公开成员函数)operatoroperator(int)operator--operator--(int)令原子值增加或减少一(公开成员函数)仅为整数类型特化fetch_and原子地进行实参和原子对象的值的逐位与并获得先前保有的值(公开成员函数)fetch_or原子地进行实参和原子对象的值的逐位或并获得先前保有的值(公开成员函数)fetch_xor原子地进行实参和原子对象的值的逐位异或并获得先前保有的值(公开成员函数)operatoroperator|operator^与原子值进行逐位与、或、异或(公开成员函数)1.用atomic原子类型来代码mutexatomic原子类型operator赋值操作示例//atomic_eaxmple1.cpp atomic example #include iostream // std::cout #include thread // std::thread #include atomic std::atomicint g_count 0; //定义原子类型 //自增函数测试 void incrementation() { printf(incrementationstart lock\n); for (int i 0; i 1000000; i) { g_count; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf(incrementationend lock\n); } int main() { std::thread th1(incrementation); std::thread th2(incrementation); //join线程 th1.join(); th2.join(); std::cout g_count g_count std::endl; //atomic::operator g_count 3000000; std::cout g_count g_count std::endl; //printf(g_count%d\n, g_count); return 0; }编译运行以上结果可以看出使用atomic原子类型后在多线程中达到了mutex的效果。同时atomic原子类型也支持赋值操作。2. is_lock_free()函数检查当前原子变量在底层实现中是否使用了“免锁Lock-Free”机制。更直白地说它就是问编译器“对这个变量进行读写操作时底层用的是 CPU 硬件的原子指令快还是偷偷用了全局互斥锁慢且可能导致线程阻塞”//atomic_eaxmple1.cpp atomic example #include iostream // std::cout #include thread // std::thread #include atomic std::atomicint g_count 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf(incrementationstart lock\n); for (int i 0; i 1000000; i) { g_count; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf(incrementationend lock\n); } int main() { std::thread th1(incrementation); std::thread th2(incrementation); //join线程 th1.join(); th2.join(); std::cout g_count g_count std::endl; //atomic::operator g_count 3000000; std::cout g_count g_count std::endl; printf(g_count 是否免锁%s\n, g_count.is_lock_free() ? true : false); printf(std::atomicA 是否免锁%s\n, std::atomicA{}.is_lock_free() ? true : false); printf(std::atomicB 是否免锁%s\n, std::atomicB{}.is_lock_free() ? true : false); //printf(g_count%d\n, g_count); return 0; }编译运行从结果验证简单类型的数据是免锁的操作快。复杂类型的操作慢就是采用mutex原理。3.store函数以非原子实参替换原子对象的值//atomic_eaxmple1.cpp atomic example #include iostream // std::cout #include thread // std::thread #include atomic std::atomicint g_count 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf(incrementationstart lock\n); for (int i 0; i 1000000; i) { g_count; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf(incrementationend lock\n); } int main() { std::thread th1(incrementation); std::thread th2(incrementation); //join线程 th1.join(); th2.join(); std::cout g_count g_count std::endl; //1. atomic::operator g_count 3000000; std::cout g_count g_count std::endl; //2. is_lock_free() printf(g_count 是否免锁%s\n, g_count.is_lock_free() ? true : false); printf(std::atomicA 是否免锁%s\n, std::atomicA{}.is_lock_free() ? true : false); printf(std::atomicB 是否免锁%s\n, std::atomicB{}.is_lock_free() ? true : false); //3. atomic::store() g_count.store(3200000); std::cout g_count g_count std::endl; //printf(g_count%d\n, g_count); return 0; }运行结果4.load()函数 获得原子对象的值。上面示例可以看到用printf函数直接输出g_count是什么报错的g_count是原子对象并不是一个整型数据cout可以打印是因为它解析了这种类型。//atomic_eaxmple1.cpp atomic example #include iostream // std::cout #include thread // std::thread #include atomic std::atomicint g_count 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf(incrementationstart lock\n); for (int i 0; i 1000000; i) { g_count; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf(incrementationend lock\n); } int main() { std::thread th1(incrementation); std::thread th2(incrementation); //join线程 th1.join(); th2.join(); std::cout g_count g_count std::endl; //1. atomic::operator g_count 3000000; std::cout g_count g_count std::endl; //2. is_lock_free() printf(g_count 是否免锁%s\n, g_count.is_lock_free() ? true : false); printf(std::atomicA 是否免锁%s\n, std::atomicA{}.is_lock_free() ? true : false); printf(std::atomicB 是否免锁%s\n, std::atomicB{}.is_lock_free() ? true : false); //3. atomic::store() g_count.store(3200000); std::cout g_count g_count std::endl; //4. atomic::load() printf(g_count%d\n, g_count.load()); return 0; }编译运行5. opration T 加载并返回原子变量的当前值。等价于load()//atomic_eaxmple2.cpp atomic::operator/operator T example: #include iostream // std::cout #include thread // std::thread #include atomic std::atomicint foo 0; std::atomicint bar 0; std::atomicfloat fat 2.3; void set_foo(int x) { foo x; printf(foo%d\n, foo.load()); } void copy_foo_to_bar() { while (foo 0) std::this_thread::yield();//让出CPU由“运行”态转为“就绪”态 // bar foo; 不能直接这样赋值 bar static_castint(foo); //取foo的值赋给bar } void copy_fat_to_foo() { // foo fat; 不能直接这样赋值 //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 foo static_castint(fat); //取fat的值赋给bar } void print_bar() { while (bar 0) std::this_thread::yield();//让出CPU由“运行”态转为“就绪”态 printf(bar%d\n, bar.load()); } int main() { std::thread first(print_bar); std::thread second(set_foo, 10); std::thread third(copy_foo_to_bar); std::thread fourth(copy_fat_to_foo); first.join(); second.join(); third.join(); fourth.join(); printf(foo%d\n, foo.load()); return 0; }运行结果6. exchange函数 替换底层值//atomic_eaxmple3.cpp atomic::exchange T example: //#include iostream // std::cout #include thread // std::thread #include atomic #include vector std::atomicint foo 5; std::atomicint bar 6; std::atomicbool ready(false); std::atomicbool 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)) { printf(thread #%d won!\n, id); } }; int main() { printf(foo%d\n, foo.load());//5 printf(bar%d\n, bar.load()); foo.exchange(bar); //等同于store(bar) printf(foo%d\n, foo.load());//6 printf(bar%d\n, bar.load()); foo.exchange(9); printf(foo%d\n, foo.load());//9 printf(bar%d\n, bar.load()); std::vectorstd::thread threads; printf(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; }编译运行7. fetch_add函数将实参加到存储于原子对象的值上并返回先前保有的值//atomic_eaxmple4.cpp atomic::fetch_add example: //#include iostream // std::cout #include thread // std::thread #include atomic #include array std::atomicint foo{ 5 }; std::atomiclong long data{ 10 }; std::arraylong long, 5 return_values{}; void do_work(int thread_num) { long long val data.fetch_add(1, std::memory_order_relaxed); return_values[thread_num] val; } int main() { printf(foo%d\n, foo.load());//5 int ret foo.fetch_add(2); printf(foo%d\n, foo.load());//7 std::thread th0{ do_work, 0 }; std::thread th1{ do_work, 1 }; std::thread th2{ do_work, 2 }; std::thread th3{ do_work, 3 }; std::thread th4{ do_work, 4 }; th0.join(); th1.join(); th2.join(); th3.join(); th4.join(); printf(result %lld\n, data.load()); for (long long val : return_values) printf(look return value: %lld\n, val); return 0; }编译运行8.fetch_sub函数从存储于原子对象的值减去实参并获得先前保有的值//atomic_eaxmple5.cpp atomic::fetch_sub example: //#include iostream // std::cout #include thread // std::thread #include atomic #include array std::atomicint foo{ 5 }; std::atomicint data{ 66 }; std::arrayint, 5 return_values{}; void do_work(int thread_num) { int val data.fetch_sub(2, std::memory_order_relaxed); return_values[thread_num] val; } int main() { printf(foo%d\n, foo.load());//5 int ret foo.fetch_sub(2); printf(foo%d\n, foo.load());//3 std::thread th0{ do_work, 0 }; std::thread th1{ do_work, 1 }; std::thread th2{ do_work, 2 }; std::thread th3{ do_work, 3 }; std::thread th4{ do_work, 4 }; th0.join(); th1.join(); th2.join(); th3.join(); th4.join(); printf(result %d\n, data.load()); for (int val : return_values) printf(look return value: %d\n, val); return 0; }编译运行9.operatoroperator- 原子值进行加、减operatoroperator--令原子值增加或减少一//atomic_eaxmple6.cpp operatoroperator-operatoroperator-- example: //#include iostream // std::cout #include thread // std::thread #include atomic #include array std::atomicint foo{ 5 }; std::atomicint bar{ 6 }; int main() { printf(foo%d\n, foo.load());//5 foo 2;//加常数 printf(foo%d\n, foo.load());//7 foo - 3; printf(foo%d\n, foo.load());//4 foo bar; //加原子对象 printf(foo%d\n, foo.load());//10 foo--; printf(foo%d\n, foo.load());//9 bar; printf(bar%d\n, bar.load());//7 return 0; }编译运行10.fetch_and原子地进行实参和原子对象的值的逐位与并获得先前保有的值fetch_or原子地进行实参和原子对象的值的逐位或并获得先前保有的值fetch_xor 原子地进行实参和原子对象的值的逐位异或并获得先前保有的值operator operator| operator^ 与原子值进行逐位与、或、异或//atomic_eaxmple7.cpp fetch_andfetch_orfetch_xor operator, operator|, operator^ example: //#include iostream // std::cout #include thread // std::thread #include atomic std::atomicint foo{ 5 }; int main() { printf(foo%d\n, foo.load());//5 int ret foo. fetch_and (3); //1 printf(ret %d, foo%d\n, ret, foo.load()); ret foo.fetch_or(5);//5 printf(ret %d, foo%d\n, ret, foo.load()); ret foo.fetch_xor(2); //7 printf(ret %d, foo%d\n, ret, foo.load()); foo 2;//2 printf(ret %d, foo%d\n, ret, foo.load()); foo | 3; //3 printf(ret %d, foo%d\n, ret, foo.load()); foo ^ 5; //6 printf(ret %d, foo%d\n, ret, foo.load()); return 0; }编译运行