我在Linux和Mac OS X之间使用pthreads的程序的行为遇到了一个奇怪的差异。
考虑下面的程序,可以使用“gcc -pthread -o threadtest threadtest.c”进行编译:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> static void *worker(void *t) { int i = *(int *)t; printf("Thread %d started\n", i); system("sleep 1"); printf("Thread %d ends\n", i); return (void *) 0; } int main() { #define N_WORKERS 4 pthread_t workers[N_WORKERS]; int args[N_WORKERS]; int i; for (i = 0; i < N_WORKERS; ++i) { args[i] = i; pthread_create(&workers[i], NULL, worker, args + i); } for (i = 0; i < N_WORKERS; ++i) { pthread_join(workers[i], NULL); } return 0; }
在四核Mac OS X计算机上运行生成的可执行文件导致以下行为:
$ time ./threadtest Thread 0 started Thread 2 started Thread 1 started Thread 3 started Thread 0 ends Thread 1 ends Thread 2 ends Thread 3 ends real 0m4.030s user 0m0.006s sys 0m0.008s
请注意,实际内核的数量可能甚至不相关,因为时间仅仅是在“sleep 1”shell命令中进行的,而没有进行任何计算。 同样显而易见的是,线程是在程序启动后立即出现“Thread … started”消息时并行启动的。
在Linux机器上运行相同的testing程序给出了我期望的结果:
$ time ./threadtest Thread 0 started Thread 3 started Thread 1 started Thread 2 started Thread 1 ends Thread 2 ends Thread 0 ends Thread 3 ends real 0m1.010s user 0m0.008s sys 0m0.013s
四个进程并行启动,每个进程hibernate一秒钟,大概需要一秒钟的时间。
如果我把实际的计算放到worker()函数中,并移除system()调用,我也会在Mac OS X中看到预期的加速。
所以问题是,为什么在线程中使用system()调用有效地序列化Mac OS X上线程的执行,以及如何防止这种情况呢?
@BasileStarynkevitch和@null指出,在Mac OS X的C库中的system()实现中的全局互斥可能负责观察到的行为。 @null提供了对system()实现的潜在源文件的引用,其中包含这些操作:
#if __DARWIN_UNIX03 pthread_mutex_lock(&__systemfn_mutex); #endif /* __DARWIN_UNIX03 */ #if __DARWIN_UNIX03 pthread_mutex_unlock(&__systemfn_mutex); #endif /* __DARWIN_UNIX03 */
通过在lldb中反汇编system()函数,我证实这些调用实际上存在于编译的代码中。
解决方法是用fork()/ execve()/ waitpid()系统调用的组合来替换system()C库函数的使用。 原始示例中修改worker()函数的概念的快速证明:
static void *worker(void *t) { static const char shell[] = "/bin/sh"; static const char * const args[] = { shell, "-c", "sleep 1", NULL }; static const char * const env[] = { NULL }; pid_t pid; int i = *(int *)t; printf("Thread %d started\n", i); pid = fork(); if (pid == 0) { execve(shell, (char **) args, (char **) env); } waitpid(pid, NULL, 0); printf("Thread %d ends\n", i); return (void *) 0; }
通过这个修改,测试程序现在可以在Mac OS X上执行大约一秒钟。