概述
在多线程应用中有个很典型的业务就是共同争抢一个某一个资源,比如很典型的秒杀,很多的线程在争夺某一个资源。本文以一个最简单的例子说明,2万个线程一起争夺一个变量+1的操作。
只使用atomic
在https://my.oschina.net/u/3707404/blog/3211668中,使用atomic类型可以避免多线程加锁导致的效率问题,同时保持资源安装,但是atomic本身有也其缺陷,其不能保证访问的一致性,即当100个线程都执行+1他能保证每一个线程的操作都会成功,但是如果你访问这个变量,其不保证每一个线程都看到的是最新的值,通过下面简单的代码你就会体会到。
#include <vector> #include <queue> #include <iostream> #include <boost/thread.hpp> #include <chrono> class Task { public: std::atomic<int> queue; boost::mutex mutex; Task() { queue = 0; } void get() { if (queue == 0) { usleep(1); queue++; } } }; int main(int argc, char **argv) { auto begin = std::chrono::high_resolution_clock::now(); boost::thread_group threads; boost::shared_ptr<Task> task = boost::make_shared<Task>(); for (int i = 0; i < 5000; i++) { threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); } threads.join_all(); std::cout << "current value " << task->queue << " \n"; auto end = std::chrono::high_resolution_clock::now(); std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << "ns" << std::endl; return 0; }
输出结果
current value 6
537372675ns
加锁
接下来我们尝试加锁
#include <vector> #include <queue> #include <iostream> #include <boost/thread.hpp> #include <chrono> class Task { public: std::atomic<int> queue; boost::mutex mutex; Task() { queue = 0; } void get() { boost::lock_guard<boost::mutex> lock{mutex}; if (queue == 0) { usleep(1); queue++; } } }; int main(int argc, char **argv) { auto begin = std::chrono::high_resolution_clock::now(); boost::thread_group threads; boost::shared_ptr<Task> task = boost::make_shared<Task>(); for (int i = 0; i < 5000; i++) { threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); } threads.join_all(); std::cout << "current value " << task->queue << " \n"; auto end = std::chrono::high_resolution_clock::now(); std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << "ns" << std::endl; return 0; }
current value 1
699948447ns
加锁可以解决资源问题,但是不可避免的导致性能的浪费。
CAS操作
#include <vector> #include <queue> #include <iostream> #include <boost/thread.hpp> #include <chrono> class Task { public: std::atomic<int> queue; boost::mutex mutex; Task() { queue = 0; } void get() { int n = 0; usleep(1); if (std::atomic_compare_exchange_weak(&queue, &n, n + 1)) { std::cout << "i got the atomic add \n"; } } }; int main(int argc, char **argv) { auto begin = std::chrono::high_resolution_clock::now(); boost::thread_group threads; boost::shared_ptr<Task> task = boost::make_shared<Task>(); for (int i = 0; i < 5000; i++) { threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); threads.create_thread(boost::bind(&Task::get, task)); } threads.join_all(); std::cout << "current value " << task->queue << " \n"; auto end = std::chrono::high_resolution_clock::now(); std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << "ns" << std::endl; return 0; }
i got the atomic add
current value 1
484698816ns
可以看到使用atomic_compare_exchange_weak机制是性能最好,并同时保证了资源的安全。
总结
atomic_compare_exchange_weak是一种兼顾性能和资源安全的多线程同步模式,https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
来源:oschina
链接:https://my.oschina.net/u/3707404/blog/3211947