tags:
- Notes
printf and Concurrency
Inspired by Condition Variables.
我们有下面这段程序:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t mutex;
void mutex_init(){
int res = pthread_mutex_init(&mutex, NULL);
if (res == 0) {
printf("Mutex lock initialized successfully!\n");
} else {
printf("Failed to initialize mutex lock. Error code: %d\n", res);
}
return;
}
pthread_cond_t cond;
void cv_init(){
int res;
res = pthread_cond_init(&cond, NULL);
if (res == 0) {
printf("Condition variable initialized successfully!\n");
} else {
printf("Failed to initialize condition variable. Error code: %d\n", res);
}
return;
}
int condition = 0; // Explicitly initialize the condition variable to 0
void *thread_func(void *arg) {
printf("Thread %ld is requiring lock.\n", (long)pthread_self());
pthread_mutex_lock(&mutex);
while (!condition) {
pthread_cond_wait(&cond, &mutex);
// The thread will block here, and the mutex lock will be released until the condition variable is signaled.
}
printf("Thread %ld continues\n", (long)pthread_self());
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
mutex_init();
cv_init();
pthread_t threads[8];
for (int i = 0; i < 8; i++) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}
usleep(100000); // Sleep for a second to ensure the thread is waiting on the condition variable
printf("Main thread is about to signal the condition variable.\n");
pthread_mutex_lock(&mutex);
condition = 1;
printf("Condition fulfilled by main thread.\n");
for(int i = 0; i < 8; i++){
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
for (int i = 0; i < 8; i++) {
pthread_join(threads[i], NULL);
}
// Destroy the condition variable and mutex to release resources
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
编译后用 Helgrind 运行一下检查并发错误。发现了 249 个并发错误,为什么?
==87816== ERROR SUMMARY: 286 errors from 4 contexts (suppressed: 198 from 44)
观察一下 Helgrind 给出的日志,我发现许多 IO 相关的日志输出。那可能是和 printf
有关了。于是我马不停蹄地问 AI 发生了什么?随后 AI 给出了答复:将 printf("Thread %ld is requiring lock.\n", (long)pthread_self());
注释掉。
void *thread_func(void *arg) {
// printf("Thread %ld is requiring lock.\n", (long)pthread_self());
pthread_mutex_lock(&mutex);
while (!condition) {
pthread_cond_wait(&cond, &mutex);
// The thread will block here, and the mutex lock will be released until the condition variable is signaled.
}
printf("Thread %ld continues\n", (long)pthread_self());
pthread_mutex_unlock(&mutex);
return NULL;
}
注释掉之后运行,的确没有问题了,但是为什么?
printf
我们用 printf
来写入一些数据并打印到屏幕上,这些数据都会被存到一个缓冲区(stdout
)里。既然我们有缓冲区,那么不难理解,我们需要锁来保证有多个线程写缓冲区时不会引起数据竞争(内容交错)。然而,虽然在 C 标准中 printf()
是线程安全的。但内容交错仍然存在。
在 printf()
函数内部,有一把锁来保护 IO 缓冲区:flockfile
/ funlockfile
。也就是说每次调用 printf
都会触发以下操作:
int printf(const char *restrict format, ...){
flockfile(stdout); // Lock
// Write buffer
// Flushing the buffer
funlockfile(stdout); // Unlock
}
既然是线程安全的,为什么还会报错?一个原因就是因为 printf()
涉及到 IO 操作,速度可能慢些,如此,可能会影响线程执行的调度顺序。但即使线程被调度器调度,其他的线程也不会写缓冲区。那为什么会出现内容交错呢?