【C++】深入理解 C++ 输入输出同步机制:为什么 cin/cout 没有 scanf/printf 快?
一、同步机制
- C++ 的
cin
和cout
:cin
和cout
默认是同步的,这意味着它们会与 C 语言的标准输入输出流(stdin
和stdout
)保持同步。这种同步机制可以防止多个线程之间的冲突,但也增加了额外的开销。
- C 语言的
scanf
和printf
:scanf
和printf
没有类似的同步机制,默认情况下是异步的,因此在多线程环境下需要注意同步问题。
二、为什么要同步:
- 共享缓冲区:
cin
和cout
使用的缓冲区与stdin
和stdout
使用的缓冲区是相同的。这意味着无论你使用哪种方式读写数据,都会影响到同一个缓冲区。
- 顺序保证:
- 输入输出操作的顺序得到保证。例如,如果你先用
printf
输出一条消息,再用cout
输出另一条消息,这两条消息会按照预期的顺序输出,不会交错。
- 输入输出操作的顺序得到保证。例如,如果你先用
- 避免冲突:
- 避免了在同一程序中混合使用 C++ 流和 C 语言函数时可能出现的冲突。例如,如果你在一个线程中使用
cin
读取数据,而在另一个线程中使用scanf
读取数据,同步机制确保这两个操作不会相互干扰。
- 避免了在同一程序中混合使用 C++ 流和 C 语言函数时可能出现的冲突。例如,如果你在一个线程中使用
三、如果关闭同步机制会如何:
关闭同步机制后,cin
和 cout
不再与 stdin
和 stdout
保持同步,这可能导致以下几种情况:
1、输出顺序不保证:
-
如果你在同一个程序中混合使用
cout
和printf
,输出的顺序可能不再按预期进行。例如,cout
的输出可能会覆盖printf
的输出,或者两者之间的顺序可能会变得混乱。 -
当同步机制关闭后,
cout
和printf
的缓冲区管理变得独立。每个cout
调用和printf
调用可能会在不同的时间点刷新缓冲区,导致输出顺序变得不可预测。 -
举个例子:
#include <iostream> #include <cstdio> using namespace std; int main() { // 关闭同步机制 ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cout << "Hello from C++\n"; printf("Hello from C\n"); cout << "Goodbye from C++\n"; printf("Goodbye from C\n"); return 0; }
如果没有关闭同步,输出顺序应该是这样的:
Hello from C++
Hello from C
Goodbye from C++
Goodbye from C
但在关闭同步机制后,实际输出可能会变得不可预测:
可能是这样
Hello from C++
Goodbye from C++
Hello from C
Goodbye from C
也可能是
Hello from C
Hello from C++
Goodbye from C
Goodbye from C++
2、输入冲突:
- 如果你在同一个程序中混合使用
cin
和scanf
,可能会导致输入冲突。例如,cin
可能会读取scanf
留下的缓冲区内容,或者反之亦然。
四、进一步理解细节:
1、两个输出缓冲区,导致输出顺序不同:
关闭同步机制后,cin 和 cout 以及 scanf 和 printf 的缓冲区管理互相独立,各自有各自的一个缓冲区,而它们仍然共享同一个底层输入输出设备,这就说明一个程序内两个缓冲区对应着一个底层输入输出设备。
关系演示:关闭同步 ——> 两套输入输出的缓冲区管理独立 ——> 各自有一个缓冲区 ——> 两者共享同一个底层输入输出设备 == 一个程序内两个缓冲区对应着一个底层输入输出设备
就是因为关闭同步后,两者使用独立的缓冲区管理机制与各自独立的缓冲区,因此两者的刷新输出数据时机就各自管理,因此 输出顺序无法保证相同,就会出现顺序不一致的现象
2、一个输入缓冲区,导致读取数据错乱:
在关闭同步机制后,尽管 cin
和 scanf
使用独立的缓冲区管理机制,各自独立有一套输出缓冲区,但它们仍然共享同一个底层的输入缓冲区,即标准输入缓冲区。这个标准输入缓冲区是由操作系统管理的,用于暂存从输入设备(如键盘)接收到的数据。
用户通过键盘输入数据。
这些数据首先被暂存到操作系统的内核缓冲区中。内核缓冲区是操作系统内部用于暂存输入数据的区域。
当用户按下回车键时,内核缓冲区中的数据被刷新到用户层面的标准输入缓冲区。
cin
和 scanf
就在这个用户层面的标准输入缓冲区中读取数据
(1)输入冲突的具体原因
- 标准输入缓冲区的残留数据:
- 当
scanf
读取数据时,它可能不会完全清空标准输入缓冲区。剩余的数据可能会被后续的cin
读取。 - 同样,当
cin
读取数据时,它可能不会完全清空标准输入缓冲区,剩余的数据可能会被后续的scanf
读取。 - 例如用户通过键盘输入这样一条字符串到缓冲区:
hello world
,cin
遇到空格停止读取,当通过cin
读取时,会将hello
读取到,而将world
留在缓冲区中,而cin
又不会主动清空标准输入缓冲区,则剩余的数据可能会被后续的scanf
读取。
- 当
- 独立的缓冲区管理:
- 因为管理机制独立,读取缓冲区的时机也不一致了:
cin
和scanf
使用独立的缓冲区管理机制,它们各自决定何时从标准输入缓冲区中读取数据。
- 因为管理机制独立,读取缓冲区的时机也不一致了:
五、关闭同步机制:
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
这行代码可以关闭 cin
和 cout
与 C 语言标准输入输出流的同步机制,并解除 cin
和 cout
之间的绑定,从而显著提高性能。
注意关闭了同步,就要注意尽量不要同时使用 cin/cout 和 scanf/printf