我正在尝试理解 C++ 中的异步模型。我正在研究 4 个旨在处理异步 I/O 的库:
这些库有大量的重叠。
异步 I/O 的未来可能会在 Linux 机器上使用 io_uring。liburing 的存在是为了提供 io_uring 和io_service 的接口。然而,libunifex 还提供了io_uring_context。它们都明确使用io_uring,但用法与 Boost.Asio 的io_context和 CppCoro 的io_service类似。
liburing io_service、libunifex、io_uring_contextBoost.Asioio_context和 CppCoroio_service可以一起使用吗?如果我的代码包含所有这四个库,那么每个库都会有 1 个执行上下文吗?
本节包含 CppCoro 如何异步打开文件的示例。我相信这个模板类将在 Boost.Asio 中用于异步文件访问。libunifex 有一个 io_uring_text.cpp文档,其中包括对文件的异步写入。显然 liburing 是为了异步写入文件而存在的。
我应该只使用 io_uring 特定库进行文件访问吗? …
std::atomic<T>两者std::condition_variable都有成员wait和notify_one功能。在某些应用程序中,程序员可以选择使用其中之一来实现同步目的。这些wait函数的目标之一是它们应与操作系统协调以最大程度地减少虚假唤醒。也就是说,操作系统应该避免唤醒wait-ing 线程,直到notify_one或notify_all被调用。
在我的机器上,sizeof(std::atomic<T>)issizeof(T)和sizeof(std::condition_variable)is 72。如果排除std::atomic<T>的T成员,则std::condition_variable保留 72 字节用于其同步目的,同时sizeof(std::atomic<T>)保留 0 字节。
我的问题:我应该期望std::condition_variables 和std::atomic<T>swait函数之间有不同的行为吗?例如,是否应该std::condition_variable减少虚假唤醒?
我开始尝试通过阅读文档和示例代码来学习 Boost::Asio 。我发现事情很难理解,特别是因为该模型看起来与协程相似。
然后我决定从这个cppcon 演讲开始学习协程。在链接的演讲中,在协程使用示例中给出了以下行。该示例是在 2014 年编写的,因此语法可能与 C++20 协程不匹配。
auto conn = await Tcp::connect.Read("127.0.0.1", 1337)
Run Code Online (Sandbox Code Playgroud)
这感觉与 Boost::Asio 的既定目标相似。然而,在 Boost::Asio 文档的示例部分,有一个混合了 Boost::Asio 和 C++20 协程的示例。(我还不明白这个例子。)
Boost::Asio 和协程有什么关系?协程会取代 Boost::Asio 的部分内容吗?如果我不从事网络工作,我还应该使用 Boost::Asio 吗?std::async发送者/接收者提案在哪里适合这一切?
存在三种不同类型的“无锁”算法。并发实践中给出的定义是:
\n\n\n非正式地,“无锁”\xe2\x89\x88“不使用互斥体”==其中任何一个。
\n
我不明白为什么基于锁的算法不能落入上面给出的无锁定义。这是一个简单的基于锁的程序:
\n#include <iostream>\n#include <mutex>\n#include <thread>\n\nstd::mutex x_mut;\n\nvoid print(int& x) {\n std::lock_guard<std::mutex> lk(x_mut);\n std::cout << x;\n}\n\nvoid add(int& x, int y) {\n std::lock_guard<std::mutex> lk(x_mut);\n x += y;\n}\n\nint main() {\n\n int i = 3;\n\n std::thread thread1{print, std::ref(i)};\n\n std::thread thread2(add, std::ref(i), 4);\n\n thread1.join();\n\n thread2.join();\n\n}\nRun Code Online (Sandbox Code Playgroud)\n如果这两个线程都在运行,那么在有限数量的步骤之后,其中一个必须完成。为什么我的程序不满足“无锁”的定义?
\n考虑以下代码:
#include <iostream>
class Widget {
public:
~Widget() {
std::cout << "Destructor Called!";
}
};
void doStuff() {
Widget w;
throw 1;
}
int main() {
doStuff();
}
Run Code Online (Sandbox Code Playgroud)
因为main中没有捕获异常,所以编译器可以安排程序在throwwith~Widget未调用之后立即调用terminate。
由于标准没有指定是否调用析构函数,这是未定义的行为吗?
注意:该示例来自Fedor Pikus 在 CppCon 2015 上的演讲。
编辑: 这个问题询问编译器是否有析构函数省略的自由,但是它处于特殊的、不同的情况下。该问题的答案不适用于本问题。
我有一个非原子变量my_var和一个std::mutex my_mut. 我假设到目前为止,程序员已经遵循了以下规则:
每次程序员修改或写入时
my_var,他都会锁定和解锁my_mut。
假设这一点,Thread1执行以下操作:
my_mut.lock();
my_var.modify();
my_mut.unlock();
Run Code Online (Sandbox Code Playgroud)
以下是我在脑海中想象的事件顺序:
my_mut.lock();可能存在多个副本。my_var即使程序员遵循了规则,这些值也不一定一致。my_mut.lock();,来自先前执行的my_mut临界区的所有写入对于该线程在内存中都是可见的。my_var.modify();执行。my_mut.unlock();可能存在多个副本。my_var即使程序员遵循了规则,这些值也不一定一致。my_var在该线程结束时的值对于下一个锁定的线程来说是可见的my_mut,直到它锁定时my_mut。我一直很难找到一个来源来验证这到底是如何std::mutex工作的。我查阅了C++标准。从ISO 2013中,我找到了这一部分:
[ 注意:例如,获取互斥锁的调用将对包含互斥锁的位置执行获取操作。相应地,释放相同互斥锁的调用将在这些相同位置执行释放操作。非正式地,在 A 上执行释放操作会强制其他内存位置上的先前副作用对稍后在 A 上执行消耗或获取操作的其他线程可见。
我的理解std::mutex正确吗?
通过内存保护,我的意思是以下程序将在许多机器上抛出运行时异常:
#include <iostream>
int main() {
int* my_int = new int[12];
std::cout << my_int[20000];
delete[] my_int;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
该程序给出以下错误:
Exception thrown at 0x00007FF7A467101A in myprogram.exe: 0xC0000005: Access violation reading location 0x000002794CA635C0.
Run Code Online (Sandbox Code Playgroud)
由于每个进程都有自己的虚拟内存,因此其他程序已经受到保护,免受我的代码中的访问冲突的影响。在我看来,正确的程序会付出运行时成本,因为不正确的程序可能会访问未分配的内存。
为什么计算机在调试模式之外还要费力地防止访问违规?
编辑:这个问题的一个很好的初步答案是“这些检查通常是在硬件中完成的”。接下来的问题是“如果不需要在访问冲突上引发异常,是否可以制造出更快的硬件? ”由于 CPU 硬件优化的大部分内容都与空间有关,我认为答案是“肯定是的,但不是”足够值得了。” 我们在硬件上为访问违规检查支付了多少成本?
c++ memory-management allocation virtual-memory access-violation
我们可以转发声明一个函数:
void func1();
void func2() {
// use func1
}
void func1() {
// do stuff
}
Run Code Online (Sandbox Code Playgroud)
func1如果调用以下命令,这可能是必要的func2:
void func1();
void func2() {
func1();
}
void func1() {
if (condition) {
func2();
}
}
Run Code Online (Sandbox Code Playgroud)
虽然func1第二个示例中的内联并不困难,但我不知道在任意复杂的程序中是否存在此类编译器优化的理论障碍。在第二种情况下,前向声明是必要的,并且可能会丢失与程序的递归性质相关的编译器优化。
问题:当不需要但仍然使用前向声明时(例如第一个示例),任何主要编译器(gcc、clang、msvc)上是否会丢失任何编译器优化?
c++ optimization recursion forward-declaration compiler-optimization
c++ ×8
asynchronous ×2
atomic ×2
boost-asio ×2
allocation ×1
boost ×1
caching ×1
coroutine ×1
destructor ×1
exception ×1
io-uring ×1
lock-free ×1
lockless ×1
mutex ×1
notify ×1
optimization ×1
recursion ×1
wait ×1