只有一个线程获取过信号量
Only one thread ever acquiring semaphore
我有一个程序,其中多个线程在一个循环中获取一个二进制信号量,然后增加一个全局计数器。但是,通过打印出线程 ID,我注意到只有一个线程获取了信号量。这是我的 MRE:
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM_THREADS 10
#define MAX_COUNTER 100
struct threadCtx {
sem_t sem;
unsigned int counter;
};
static void *
threadFunc(void *args)
{
struct threadCtx *ctx = args;
pthread_t self;
bool done = false;
self = pthread_self();
while (!done) {
sem_wait(&ctx->sem);
if ( ctx->counter == MAX_COUNTER ) {
done = true;
}
else {
sleep(1);
printf("Thread %u increasing the counter to %u\n", (unsigned int)self, ++ctx->counter);
}
sem_post(&ctx->sem);
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
struct threadCtx ctx = {.counter = 0};
sem_init(&sem.ctx, 0, 1);
for (int k=0; k<NUM_THREADS; k++) {
pthread_create(threads+k, NULL, threadFunc, &ctx);
}
for (int k=0; k<NUM_THREADS; k++) {
pthread_join(threads[k], NULL);
}
sem_destroy(&ctx.sem);
return 0;
}
输出为
Thread 1004766976 increasing the counter to 1
Thread 1004766976 increasing the counter to 2
Thread 1004766976 increasing the counter to 3
...
如果我删除对 sleep
的调用,则行为更接近我的预期(即线程以看似不确定的方式被唤醒)。为什么会这样?
Why would this be?
上下文切换很昂贵,您的实施明智地减少了它们。您的线程都在争用相同的资源,试图紧密地安排它们会使性能更差,可能对整个系统来说都是如此。
因为一直获取信号量的线程永远不会用完它的时间片,所以它会一直获取资源。您有责任编写代码来完成您想要完成的工作。尽可能高效地执行代码是实现的责任,这就是它正在做的事情。
很可能,幕后发生的事情是这样的:
不断获取sempahore的线程,除了休眠的时候,总能向前推进。但是当它处于休眠状态时,其他需要信号量的线程都无法向前推进。
不断获取信号量的线程永远不会耗尽它的时间片,因为它在发生之前休眠。
因此除了在休眠时,没有任何理由让实现阻塞此线程,这意味着没有其他线程可以获取信号量。如果您不希望此线程与信号量一起休眠并阻塞其他线程,请编写不同的代码。
David Schwartz 的回答解释了底层发生的事情。也就是说,他是站在OS开发者或者硬件设计师的角度来看的。这没什么问题,但让我们从软件架构师的角度来看您的程序:
您有多个线程都在执行同一个循环。循环锁定互斥锁,*它做了一些“工作”,然后释放互斥锁。好的,但接下来它会做什么?几乎紧接着你的循环在释放互斥量后所做的几乎是它再次锁定互斥量。您的循环几乎 100% 的时间都在锁定互斥量的情况下进行“工作”。
那么,当从来没有两个或多个线程同时工作的任何机会时,运行在多个线程中使用相同的循环有什么意义?
如果您想使用线程进行并行计算,您需要find/invent安全的方式让线程在互斥量未锁定的情况下完成大部分工作。 他们应该只锁定一个互斥锁足够长的时间以 post 一个结果,或者进行另一个分配。
有时这意味着编写的代码效率低于单线程代码。但是假设程序 (A) 有一个几乎 100% 使用 CPU 的线程,而程序 (B) 使用八个 CPU 但只以 50% 的效率使用它们。哪个节目会获胜?
* 我知道,您的示例使用了 sem_t
(信号量)对象。但是“信号量”是您正在使用的什么。 “Mutex”是您使用它的角色。
我有一个程序,其中多个线程在一个循环中获取一个二进制信号量,然后增加一个全局计数器。但是,通过打印出线程 ID,我注意到只有一个线程获取了信号量。这是我的 MRE:
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM_THREADS 10
#define MAX_COUNTER 100
struct threadCtx {
sem_t sem;
unsigned int counter;
};
static void *
threadFunc(void *args)
{
struct threadCtx *ctx = args;
pthread_t self;
bool done = false;
self = pthread_self();
while (!done) {
sem_wait(&ctx->sem);
if ( ctx->counter == MAX_COUNTER ) {
done = true;
}
else {
sleep(1);
printf("Thread %u increasing the counter to %u\n", (unsigned int)self, ++ctx->counter);
}
sem_post(&ctx->sem);
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
struct threadCtx ctx = {.counter = 0};
sem_init(&sem.ctx, 0, 1);
for (int k=0; k<NUM_THREADS; k++) {
pthread_create(threads+k, NULL, threadFunc, &ctx);
}
for (int k=0; k<NUM_THREADS; k++) {
pthread_join(threads[k], NULL);
}
sem_destroy(&ctx.sem);
return 0;
}
输出为
Thread 1004766976 increasing the counter to 1
Thread 1004766976 increasing the counter to 2
Thread 1004766976 increasing the counter to 3
...
如果我删除对 sleep
的调用,则行为更接近我的预期(即线程以看似不确定的方式被唤醒)。为什么会这样?
Why would this be?
上下文切换很昂贵,您的实施明智地减少了它们。您的线程都在争用相同的资源,试图紧密地安排它们会使性能更差,可能对整个系统来说都是如此。
因为一直获取信号量的线程永远不会用完它的时间片,所以它会一直获取资源。您有责任编写代码来完成您想要完成的工作。尽可能高效地执行代码是实现的责任,这就是它正在做的事情。
很可能,幕后发生的事情是这样的:
不断获取sempahore的线程,除了休眠的时候,总能向前推进。但是当它处于休眠状态时,其他需要信号量的线程都无法向前推进。
不断获取信号量的线程永远不会耗尽它的时间片,因为它在发生之前休眠。
因此除了在休眠时,没有任何理由让实现阻塞此线程,这意味着没有其他线程可以获取信号量。如果您不希望此线程与信号量一起休眠并阻塞其他线程,请编写不同的代码。
David Schwartz 的回答解释了底层发生的事情。也就是说,他是站在OS开发者或者硬件设计师的角度来看的。这没什么问题,但让我们从软件架构师的角度来看您的程序:
您有多个线程都在执行同一个循环。循环锁定互斥锁,*它做了一些“工作”,然后释放互斥锁。好的,但接下来它会做什么?几乎紧接着你的循环在释放互斥量后所做的几乎是它再次锁定互斥量。您的循环几乎 100% 的时间都在锁定互斥量的情况下进行“工作”。
那么,当从来没有两个或多个线程同时工作的任何机会时,运行在多个线程中使用相同的循环有什么意义?
如果您想使用线程进行并行计算,您需要find/invent安全的方式让线程在互斥量未锁定的情况下完成大部分工作。 他们应该只锁定一个互斥锁足够长的时间以 post 一个结果,或者进行另一个分配。
有时这意味着编写的代码效率低于单线程代码。但是假设程序 (A) 有一个几乎 100% 使用 CPU 的线程,而程序 (B) 使用八个 CPU 但只以 50% 的效率使用它们。哪个节目会获胜?
* 我知道,您的示例使用了 sem_t
(信号量)对象。但是“信号量”是您正在使用的什么。 “Mutex”是您使用它的角色。