再一次的参考IT的万能定律—银弹,如果一个问题不好解决,那就加一层抽象层。所以平常使用的printf()函数并非是操作系统直接提供的API,而是由运行库在操作系统API (如write()系统调用 )基础上封装来的函数。简单地说,运行库为IO读写添加的一层抽象层的基本策略是在系统调用( read()或write() )基础上额外配备缓冲区和采用“延迟写”策略,如果你对细节感兴趣可以参考我的另一篇文章——运行库:Windows下MSVC CRT运行库封装fread()函数解析。其中printf()函数的”批量预读“和”延迟写“策略在单线程的环境下并不会影响我们的正常使用,但是printf()函数的”延迟写“碰上多线程环境则可能让最后输出结果并不能按照预想的那样。
以printf函数为例,”延迟写“策略思想是指用户态下调用printf()函数,除非满足以下四种条件,否则并不立即从用户态转入内核态调用系统API输出,而是先保存在缓冲区(缓冲操作依旧运行在用户态下,故而免去了内核切换状态),直到满足以下四种条件,才切换到内核态,进行集中输出
1. 缓冲区填满;
2. 写入的字符中有”\n” “\r”;
3. 调用fflush手动刷新缓冲区;
4. 调用scanf从缓冲区中立即读取数据,也会隐式调用fflush。
通过以下的小程序,可以测试在在Linux系统上为IO写操作配备的缓冲区大小
printfBuffer.cpp
#include
#include
int main (int argc, char* argv[])
{
int i = 0x61; //0x61是a的ACSCII码
while (1)
{
printf("%c", i);
i++;
i = (i - 0x61) % 26 + 0x61;
usleep(1000);
}
return 0;
}
在屏幕第一次输出信息时,立即按下crtl+C,然后数一数屏幕中的字母个数便可以判断buffer大小,可以数一数,buffer_size = 1024 byte。这个程序改装后在Windows下运行并不会得到合适的效果(Windows下是在实时输出的,没有Linux下明显的停顿后一次性刷出一屏幕的效果,然后过一会再刷出一屏幕)。
#include
#include
using namespace std;
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter);
int main()
{
HANDLE hThread[2] = {0};
hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
for (int i=0; i<2; i++)
CloseHandle( hThread[i] );
return 0;
}
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter)
{
//cout<<" the first thread:"<<" Hello "<1);
printf("***The First Thread Range***\n");
printf("该线程的线程编号NO.%d\n", GetCurrentThreadId());
return 0;
}
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter)
{
//cout<<" the second thread:"<<" World"<"***The Second Thread Range***\n");
printf("the current thread NO.%d\n", GetCurrentThreadId());
return 0;
}
编译命令g++ multiple-thread-use-printf.cpp -lpthread -finput-charset=GB2312
正常情况下应该输出
或者输出
但是实际调用过程中,结果会看到偶尔会出现两个线程的输出顺序彼此交叉(但是很少看到printf单步调用的内容彼此交叉,不过为了保险起见还是如果printf单步调用的内容中没有使用\n,还是应该会用fflush(stdout))。
所以应该使用锁机制,保证使用标准输出stdout字符设备时按照设定的顺序批量输出。
multiple_thread_use_printf_with_mutex.cpp
#include
#include
using namespace std;
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter);
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter);
HANDLE g_hMutex = NULL;
int main()
{
g_hMutex = CreateMutex(NULL,TRUE,NULL);
HANDLE hThread[2] = {0};
hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
ReleaseMutex(g_hMutex);//释放互斥量
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
for (int i=0; i<2; i++)
CloseHandle( hThread[i] );
CloseHandle( g_hMutex );
return 0;
}
DWORD WINAPI ThreadProc1(__in LPVOID lpParameter)
{
WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
printf("***The First Thread Range***\n");
printf("该线程的线程编号NO.%d\n", GetCurrentThreadId());
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}
DWORD WINAPI ThreadProc2(__in LPVOID lpParameter)
{
WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
printf("***The Second Thread Range***\n");
printf("the current thread NO.%d\n", GetCurrentThreadId());
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}