在以往的C++标准中并没有对原子操作进行规定,在新标准C++11,引入了原子操作的概念,并通过头文件#include <atomic>提供了多种原子操作数据类型,这些类型上的所有操作都是原子的。如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。
此头文件主要声明了两大类原子对象:std::atomic_flag和std::atomic,另外还声明了一套C风格的原子类型和与C兼容的原子操作的函数
一.std::atomic_flag
std::automic_flag是bool型原子对象,执行test_and_set和clear两种操作。
结合test_and_set()和clear(),std::atomic_flag对象可以当作一个简单的自旋锁(spin lock)使用。如果在初始化时没有明确指定宏ATOMIC_FLAG_INIT,那么新创建的std::atomic_flag对象的状态是未指定的(unspecified),既没有被set也没有被clear;如果使用该宏初始化,该std::atomic_flag对象在创建时处于clear状态。
1.test_and_set
返回该std::atomic_flag对象当前状态。检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。该函数是原子的。
2.clear
清除std::atomic_flag对象的标志位,即设置std::atomic_flag的值为false
// using atomic_flag as a lock
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <sstream>
std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;
void append_number(int x) {
while (lock_stream.test_and_set()) {}
stream << "thread #" << x << '\n';
lock_stream.clear();
}
int main ()
{
std::vector<std::thread> threads;
for (int i=1; i<=10; ++i)
{
threads.push_back(std::thread(append_number,i));
}
for (auto& th : threads)
{
th.join();
}
std::cout << stream.str();
return 0;
}
二.std::atomic
std::atomic是类模板,提供了比std::atomic_flag更加完善的功能。std::atomic对int, char, bool等数据结构进行原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险。利用std::atomic可实现数据结构的无锁设计。
与std::atomic_flag类似,std::atomic也提供了一个宏ATOMIC_VAR_INIT(val)用于初始化std::atomic对象,例如std::atomic<int> a = ATOMIC_VAR_INIT(1)。提供这个宏的目的是为了兼容C实现(This macro exists for compatibility with C implementations, in which it is used as a constructor-like function for(default-constructed) atomic objects),在C++中,可以直接通过构造函数初始化,例如std::atomic<int> a(1)
#include <iostream>
#include <atomic>
#include <vector>
#include <thread>
#include <sstream>
std::atomic<bool> ready(false);
std::atomic_flag winner = ATOMIC_FLAG_INIT;
void count1m(int i)
{
while (!ready) { std::this_thread::yield(); } // wait for the ready signal
for (volatile int i = 0; i < 1000000; ++i) {} // go!, count to 1 million
if (!winner.test_and_set())
{
std::cout << "winner: " << i << std::endl;
}
}
int main()
{
std::vector<std::thread> ths;
for (int i=0; i<10; i++)
{
ths.push_back(std::thread(count1m, i));
}
ready = true;
for (int i=0; i<10; i++)
{
ths[i].join();
}
return 0;
}
在本例中,执行read=true之后,所有线程结束空等。winner被初始化为ATOMIC_FLAG_INIT,最先执行winner.test_and_set并返回false的线程为winner。
#include <iostream>
#include <atomic>
#include <vector>
#include <thread>
#include <sstream>
std::atomic<int> foo(0);
void set_foo(int x)
{
foo = x;
}
void print_foo()
{
while (foo == 0)
{
std::this_thread::yield();
}
std::cout << "foo: " << foo << std::endl;
}
int main()
{
std::thread print_th(print_foo);
std::thread set_th(set_foo, 10);
print_th.join();
set_th.join();
return 0;
}
set_foo用于设置atomic<int>对象的值,print_foo用于打印atomic<int>对象的值,毫无疑问,输出打印foo: 10。std::atomic对象的值的读取和写入还可使用load和store实现。
#include <iostream>
#include <atomic>
#include <vector>
#include <thread>
std::atomic<int> foo(0);
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void add_foo()
{
while (1)
{
foo++;
while (lock.test_and_set());
std::cout << "add: " << foo << std::endl;
lock.clear();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
void sub_foo()
{
while (1)
{
foo--;
while (lock.test_and_set());
std::cout << "sub: " << foo << std::endl;
lock.clear();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
int main()
{
std::thread th2 = std::thread(add_foo);
std::thread th1 = std::thread(sub_foo);
th1.join();
th2.join();
return 0;
}
atomic<int>支持++和--的原子操作
除了直接使用std::atomic<>类型模板外,还可以使用下表中的原子类型集,如下表所示
原子类型 | 相关特化类 |
---|---|
atomic_bool | std::atomic<bool> |
atomic_char | std::atomic<char> |
atomic_schar | std::atomic<signed char> |
atomic_uchar | std::atomic<unsigned char> |
atomic_int | std::atomic<int> |
atomic_uint | std::atomic<unsigned> |
atomic_short | std::atomic<short> |
atomic_ushort | std::atomic<unsigned short> |
atomic_long | std::atomic<long> |
atomic_ulong | std::atomic<unsigned long> |
atomic_llong | std::atomic<long long> |
atomic_ullong | std::atomic<unsigned long long> |
atomic_char16_t | std::atomic<char16_t> |
atomic_char32_t | std::atomic<char32_t> |
atomic_wchar_t | std::atomic<wchar_t> |
三.atomic与mutex性能对比
在本文开头提到原子操作避免了锁的使用,提高了效率。下面将对此进行验证
1.使用mutex
#include <iostream>
#include <ctime>
#include <mutex>
#include <vector>
#include <thread>
std::mutex mtx;
int count = 0;
void threadFun()
{
for (int i = 0; i < 10000; i++)
{
mtx.lock();
count++;
mtx.unlock();
}
}
int main(void)
{
clock_t start_time = clock();
std::vector<std::thread> ths;
for (int i = 0; i < 10; i++)
{
ths.push_back(std::thread(threadFun));
}
for (auto&th : ths)
{
th.join();
}
std::cout << "Count number:" << count << std::endl;
clock_t end_time = clock();
std::cout << "Elapse: " << end_time - start_time << "ms" << std::endl;
return 0;
}
2.使用atomic
#include <iostream>
#include <ctime>
#include <atomic>
#include <vector>
#include <thread>
std::atomic_int count(0);
void threadFun()
{
for (int i = 0; i < 10000; i++)
{
count++;
}
}
int main(void)
{
clock_t start_time = clock();
std::vector<std::thread> ths;
for (int i = 0; i < 10; i++)
{
ths.push_back(std::thread(threadFun));
}
for (auto&th : ths)
{
th.join();
}
std::cout << "Count number:" << count << std::endl;
clock_t end_time = clock();
std::cout << "Elapse: " << end_time - start_time << "ms" << std::endl;
return 0;
}
总结:从上面的截图可以发现,第一张图用时5ms,第二张图用时2ms,使用原子操作能大大的提高程序的运行效率。
原文链接:https://blog.csdn.net/caoshangpa/article/details/51712643