我在一台装有Apple M1的笔记本中,它有4个内核。我有一个问题,可以通过动态生成数百万个线程来并行解决。由于pthread_create
开销很大,因此我通过在后台留下 3 个线程来避免它。这些线程等待任务到达:
void *worker(void *arg) {
u64 tid = (u64)arg;
// Loops until the main thread signals work is complete
while (!atomic_load(&done)) {
// If there is a new task for this worker...
if (atomic_load(&workers[tid].stat) == WORKER_NEW_TASK) {
// Execute it
execute_task(&workers[tid]);
}
}
return 0;
}
这些线程生成一次pthread_create
:
pthread_create(&workers[tid].thread, NULL, &normal_thread, (void*)tid)
每当我需要完成新任务时,我只需选择一个空闲的工作线程并将任务发送给它,而不是再次调用pthread_create
:
workers[tid].stat = WORKER_NEW_TASK
workers[tid].task = ...
问题是:出于某种原因,将这 3 个线程留在后台会使我的主线程慢 25%。由于我的 CPU 有 4 个内核,我希望这 3 个线程根本不影响主线程。
为什么后台线程会减慢主线程的速度?我做错了什么吗?while (!atomic_load(&done))
循环是否消耗大量 CPU 功率?
问题是我有一个这样的线程:
typedef struct {
atomic_int has_work;
} Worker;
Worker workers[MAX_THREADS];
void *worker(void *arg) {
u64 tid = (u64)arg;
while (1) {
if (atomic_load(workers[tid].has_work)) {
// do stuff
}
}
}
(...)
请注意我如何使用原子标志has_work
来"唤醒"工作线程。正如@WhozCraig在他的评论中指出的那样,旋转查看原子标志可能不是一个好主意。如果我理解正确的话,这是计算密集型的,这会压倒 CPU。他的建议是将互斥锁与条件变量一起使用,如Butenhof的"使用POSIX Threads编程"第3.3节所述。此堆栈溢出答案有一个片段,显示了该模式的常见用法。生成的代码应如下所示:
typedef struct {
int has_work;
pthread_mutex_t has_work_mutex;
pthread_cond_t has_work_signal;
}
Worker workers[MAX_THREADS];
void *worker(void *arg) {
u64 tid = (u64)arg;
while (1) {
pthread_mutex_lock(&workers[tid].has_work_mutex);
while (!workers[tid].has_work) {
pthread_cond_wait(&workers[tid].has_work_signal, &workers[tid].has_work_mutex);
}
// do stuff
pthread_mutex_unlock(&workers[tid].has_work_mutex);
}
return 0;
}
请注意has_work
是如何被替换为使用普通int
而不是原子,并用mutex
保护它。然后,"条件变量"与该互斥锁相关联。这允许我们使用pthread_cond_wait
对线程进行睡眠排序,直到另一个线程发出"has_work">可能是真的信号。这与使用原子学的版本具有相同的效果,但对 CPU 的需求更少,并且应该性能更好。