atomic原子对象的学习
在上一篇mutex的学习中,我们知道,在多线程环境下对一个变量同时进很读写,必须要加锁才能避免数据竞争。如果只是对一个简单类型的变量进行加锁,解锁,感觉是不是有点冗余,其实可以通过atomic类型达到同样的效果,那么是atomic原子类型呢?
原子类型的对象包含一个特定类型(T)的值。
原子对象的主要特点是,从不同线程访问该内部值时不会引发数据竞争(即,只要访问顺序正确,则这种行为是明确定义的)。通常情况下,对于所有其他对象,若在并发访问同一对象时可能引发数据竞争,则该操作被视为未定义行为。
此外,原子对象还能够通过指定不同的内存顺序,在其线程中同步访问其他非原子对象。
本篇记录atomic原子对象的学习。
以下是atomic常函数
成员函数
(构造函数) | 构造原子对象 (公开成员函数) |
operator= | 存储值于原子对象 (公开成员函数) |
is_lock_free | 检查原子对象是否免锁 (公开成员函数) |
store | 原子地以非原子实参替换原子对象的值 (公开成员函数) |
load | 原子地获得原子对象的值 (公开成员函数) |
operator T | 从原子对象加载值 (公开成员函数) |
exchange | 原子地替换原子对象的值并获得它先前持有的值 (公开成员函数) |
compare_exchange_weakcompare_exchange_strong | 原子地比较原子对象与非原子实参的值,相等时进行原子交换,不相等时进行原子加载 (公开成员函数) |
wait (C++20) | 阻塞线程直到被提醒且原子值更改 (公开成员函数) |
notify_one (C++20) | 提醒至少一个在原子对象上的等待中阻塞的线程 (公开成员函数) |
notify_all (C++20) | 提醒所有在原子对象上的等待中阻塞的线程 (公开成员函数) |
常量 | |
is_always_lock_free [静态] (C++17) | 指示该类型是否始终免锁 (公开静态成员常量) |
特化成员函数
为整数、浮点数(C++20 起)和指针类型特化 | |
fetch_add | 原子地将实参加到存储于原子对象的值上,并返回先前保有的值 (公开成员函数) |
fetch_sub | 原子地从存储于原子对象的值减去实参,并获得先前保有的值 (公开成员函数) |
operator+=operator-= | 与原子值进行加、减 (公开成员函数) |
仅为整数和指针类型特化 | |
fetch_max (C++26) | 原子地进行实参和原子对象的值的 std::max,并获得先前保有的值 (公开成员函数) |
fetch_min (C++26) | 原子地进行实参和原子对象的值的 std::min,并获得先前保有的值 (公开成员函数) |
operator++operator++(int)operator--operator--(int) | 令原子值增加或减少一 (公开成员函数) |
仅为整数类型特化 | |
fetch_and | 原子地进行实参和原子对象的值的逐位与,并获得先前保有的值 (公开成员函数) |
fetch_or | 原子地进行实参和原子对象的值的逐位或,并获得先前保有的值 (公开成员函数) |
fetch_xor | 原子地进行实参和原子对象的值的逐位异或,并获得先前保有的值 (公开成员函数) |
operator&=operator|=operator^= | 与原子值进行逐位与、或、异或 (公开成员函数) |
1.用atomic原子类型来代码mutex,atomic原子类型operator=赋值操作示例
//atomic_eaxmple1.cpp atomic example #include <iostream> // std::cout #include <thread> // std::thread #include <atomic> std::atomic<int> g_count = 0; //定义原子类型 //自增函数测试 void incrementation() { printf("incrementation========================>start lock\n"); for (int i = 0; i < 1000000; ++i) { g_count++; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf("incrementation========================>end 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::atomic<int> g_count = 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf("incrementation========================>start lock\n"); for (int i = 0; i < 1000000; ++i) { g_count++; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf("incrementation========================>end 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::atomic<A> 是否免锁?%s\n", std::atomic<A>{}.is_lock_free() ? "true" : "false"); printf("std::atomic<B> 是否免锁?%s\n", std::atomic<B>{}.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::atomic<int> g_count = 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf("incrementation========================>start lock\n"); for (int i = 0; i < 1000000; ++i) { g_count++; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf("incrementation========================>end 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::atomic<A> 是否免锁?%s\n", std::atomic<A>{}.is_lock_free() ? "true" : "false"); printf("std::atomic<B> 是否免锁?%s\n", std::atomic<B>{}.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::atomic<int> g_count = 0; //定义原子类型 struct A { int a[100]; }; struct B { int x, y; }; //自增函数测试 void incrementation() { printf("incrementation========================>start lock\n"); for (int i = 0; i < 1000000; ++i) { g_count++; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf("incrementation========================>end 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::atomic<A> 是否免锁?%s\n", std::atomic<A>{}.is_lock_free() ? "true" : "false"); printf("std::atomic<B> 是否免锁?%s\n", std::atomic<B>{}.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::atomic<int> foo = 0; std::atomic<int> bar = 0; std::atomic<float> 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_cast<int>(foo); //取foo的值赋给bar } void copy_fat_to_foo() { // foo = fat; 不能直接这样赋值 //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 foo = static_cast<int>(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::atomic<int> foo = 5; std::atomic<int> bar = 6; 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)) { 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::vector<std::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::atomic<int> foo{ 5 }; std::atomic<long long> data{ 10 }; std::array<long 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::atomic<int> foo{ 5 }; std::atomic<int> data{ 66 }; std::array<int, 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.operator+=,operator-= 原子值进行加、减,operator++,operator--令原子值增加或减少一
//atomic_eaxmple6.cpp operator+=,operator-=,operator++,operator-- example: //#include <iostream> // std::cout #include <thread> // std::thread #include <atomic> #include <array> std::atomic<int> foo{ 5 }; std::atomic<int> 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_and,fetch_or,fetch_xor ,operator&=, operator|=, operator^= example: //#include <iostream> // std::cout #include <thread> // std::thread #include <atomic> std::atomic<int> 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; }编译运行:
