《C++ Concurrency in Action》笔记23 std::atomic<bool>

本文介绍了std::atomic<bool>的基本特性和使用方法,包括如何进行读-改-写操作和比较/交换操作。详细解释了compare_exchange_weak()与compare_exchange_strong()的区别及其应用场景。
摘要由CSDN通过智能技术生成

5.2.3 std::atomic<bool>的相关操作

最基本的内置类型就是std::atomic<bool>,这是个比atomic_flag更为全面的bool量。尽管它仍然不支持拷贝构造和拷贝赋值,但是它可以从一个非原子类型的bool量构造,也可以用一个非原子类型来给std::atomic<bool>赋值:

std::atomic<bool> b(true);
b=false;

需要注意的是,复制操作并不像我们平时那样返回对象引用,而是返回一个bool值。这是另一个原子类型的特点:为了避免其他代码为了从一个引用获取其值之前被其他线程改变。通过返回一个非原子类型的值,能够确保此刻得到的值就是当前原子对象中存储的实际值。

相比于atomic_flog,test_and_set()函数被替换成exchange(),设置新的值,返回旧的值。想获取其值既可以使用load(),也可以隐式的转换。同样,想设置它的值,既可以使用store()函数也可以使用隐式转换。

atomic<bool>不仅支持exchange()这样的read-modify-write类型的操作,还支持“比较/交换”操作。

根据当前值决定是否存储新的值

这种操作叫做“比较/交换”,有两个这样的成员函数:compare_exchange_weak()、compare_exchange_strong()。

compare_exchange_weak()的一个函数声明是:

bool atomic<bool>::compare_exchange_weak( bool& expected_value, bool new_value, memory_order _Order) noexcept;
参数expected_value代表在执行这个函数之前的一个期待值,或者说预测值。

函数首先比较atomic<bool>中的值是否与expected_value想等,如果想等则将new_value的值保存到atomic<bool>中,返回true;如果不相等,则将atomic<bool>当前的值保存到expected_value中,返回false。

对于compare_exchange_weak()函数来说,即使expected_value的值与atomic<bool>的当前值想等,仍然有可能交换失败并返回false。这最可能发生在缺乏单个比较/交换指令的机器上,如果处理器不能保证比较/交换操作被原子化,那么当线程数大于处理器核数时,可能发生一个线程在操作一半的时候被切出,此时另一个线程切入并继续做了某些操作。这被叫做虚假失败(spurious failure)。

根据www.cplusplus.com的描述:On these spurious failures, the function returns false while not modifying expected.

如果出现虚假失败,这个函数返回false,并且没有修改expected参数。

因为compare_exchange_weak()可能出现虚假失败,因此必须使用循环调用的方式:

bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);
如果是真的失败,那么compare_exchange_weak()的返回值应该是false、并且expected的值应该是true,那么循环就终止了。除非compare_exchange_weak()的返回值是false,但expected的值依然是false,这明显代表是假失败,因此循环将继续。

另一个函数compare_exchange_strong()则不同,它保证只有std::atomic<bool>对象的值不等于expected的值时才返回false。这样就没必要使用循环了。因为当他返回false时,说明操作的确是失败的。其实在某些机器,某些算法上,compare_exchange_strong()函数内部包含了循环,其性能会比compare_exchange_weak()差一些。

如果你无论如何都想更新当前原子变量的值(或许想更新为一个依赖当前值的新值),那么就需要每次更新expected的值。如果此时没有其他线程在修改原子变量,那么compare_exchange_weak()和compare_exchange_strong()两个函数都能保证在循环时执行成功。如果获取expected值的耗时很小,那么循环使用compare_exchange_weak()是个好的选择;如果获取expected值的耗时很大,那么如果expected的值没有改变的话,使用compare_exchange_strong()更好,因为它不需要重新获取expected的值。对于 std::atomic<bool>来说,这可能无关紧要,但是对于其他原子类型,这可能是个不得不面对的问题。

比较/交换操作可以接受两个内存指令参数,它们用指定执行成功或失败时使用的内存指令。比较完美的调用是,成功时使用memory_order_acq_rel指令,失败时使用memory_order_relaxed指令。一次失败的操作并没有对原子对象执行保存操作,因此不能使用memory_order_release或memory_order_acq_rel指令。你也不能为执行失败提供比执行成功更为严格的内存指令,例如,如果你想给执行失败时指定memory_order_acquire或memory_order_seq_cst指令,那么你必须给执行成功时指定同样的指令。

std::atomic<bool>与std::atomic_flag的一个不同之处是,std::atomic<bool>可能不是无锁的。为了保证原子操作,内部实现可能使用了mutex。可以使用is_lock_free()来检测。除了std::atomic_flag外,其他的标准原子类型都具有这一特点。











以下是一些可能的 C++ 语言优化方法: 1. 对于文件不存在的情况,可以使用异常机制来处理,避免直接退出程序。 ```c++ try { std::ifstream infile(filename); if (!infile) { throw std::runtime_error("文件打开失败"); } // 读取文件内容并输出到屏幕上 std::string data; while (std::getline(infile, data)) { std::cout << data << std::endl; } // 关闭文件 infile.close(); } catch (std::exception& ex) { std::cerr << ex.what() << std::endl; return 1; } ``` 2. 在读取文件时,可以使用文件流对象的 read 函数进行二进制读取,避免字符编码的转换。 ```c++ std::ifstream infile(filename, std::ios::binary); if (!infile) { std::cerr << "文件打开失败" << std::endl; return 1; } // 获取文件大小 infile.seekg(0, std::ios::end); std::size_t filesize = infile.tellg(); infile.seekg(0, std::ios::beg); // 读取文件内容并输出到屏幕上 std::vector<char> buffer(filesize); infile.read(buffer.data(), filesize); std::cout.write(buffer.data(), filesize); // 关闭文件 infile.close(); ``` 3. 在读取文件时,可以使用多线程进行处理,提高读取效率。 ```c++ std::ifstream infile(filename, std::ios::binary); if (!infile) { std::cerr << "文件打开失败" << std::endl; return 1; } // 获取文件大小 infile.seekg(0, std::ios::end); std::size_t filesize = infile.tellg(); infile.seekg(0, std::ios::beg); // 读取文件内容并输出到屏幕上 std::vector<char> buffer(filesize); std::vector<std::future<void>> futures; for (int i = 0; i < std::thread::hardware_concurrency(); ++i) { std::size_t start = i * filesize / std::thread::hardware_concurrency(); std::size_t end = (i + 1) * filesize / std::thread::hardware_concurrency(); futures.push_back(std::async(std::launch::async, [&infile, &buffer, start, end]() { infile.seekg(start, std::ios::beg); infile.read(buffer.data() + start, end - start); })); } for (auto& future : futures) { future.wait(); } std::cout.write(buffer.data(), filesize); // 关闭文件 infile.close(); ``` 4. 在读取文件时,可以使用内存映射方式进行读取。需要注意的是,内存映射方式适用于较大的文件,且需要使用操作系统提供的 API。 ```c++ #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> std::string filename = argv[1]; int fd = open(filename.c_str(), O_RDONLY); if (fd == -1) { std::cerr << "文件打开失败" << std::endl; return 1; } struct stat sb; if (fstat(fd, &sb) == -1) { std::cerr << "获取文件大小失败" << std::endl; close(fd); return 1; } char* addr = (char*)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { std::cerr << "内存映射失败" << std::endl; close(fd); return 1; } std::cout.write(addr, sb.st_size); munmap(addr, sb.st_size); close(fd); ``` 需要注意的是,内存映射方式需要手动进行内存释放和文件关闭操作。另外,对于 Windows 系统,需要使用 CreateFile 和 CreateFileMapping 等 API 来进行内存映射。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值