我想确定我理解条件变量是如何工作的,所以我会用我写的程序来问我的问题。
在我的程序中,我有一个“生产者”主题(一个)和“工作线程”(几个让我们假设 3 )。
生产者线程“处理”一个FIFO链表,这意味着,它的作用就是检查是否有一个项目(在我的程序中名为 request 的Req
类型)列表的开头(由我的程序中名为 front 的全局指针指向),如果是,则将其分配到全局请求元素(称为globalReq
)。
工作线程,循环运行,等待处理请求,通过将全局请求变量提取到自己的局部变量中(这是每个线程的“私有”导致每个线程都有一个独立的堆栈 - 如果我错了,请纠正我,然后处理请求。
为了做到这一点,我使用互斥量和条件变量。
一个重要的注意事项是,一旦请求存在(暂时让我们假设只存在一个请求),IT并不重要哪个工作线程将“照顾”它(假设它们都是“免费的” “ - 睡在条件变量上。”
在提取请求并将其分配到全局请求之后,生产者线程调用pthread_cond_signal
- 据我所知,这解除了至少一个“阻塞”线程 - >因此它可以解锁,例如2个线程。
所以我的问题是我现有的代码(下面):
1)我怎样才能保证只有一个线程(来自工作线程)将处理请求。 我是否需要在所有通用“生产者消费者”实现中添加“while check循环”?
2)通过pthread_cond_broadcast
(或者pthread_cond_signal
取消阻止多个线程)解除阻塞的线程如何通过互斥锁上的内容,可能我还没掌握它...
工作线程的(每一个)代码是:
void *worker(void *arg)
{
while(1)
{
printf("\n BEFORE LOCKING sthread_mutex with thread: %d \n", syscall(SYS_gettid));
pthread_mutex_lock(&sthread_mutex);
printf("\n AFTER UNLOCKING sthread_mutex with thread: %d \n", syscall(SYS_gettid));
printf("\n BEFORE WAITING ON cond_var with thread: %d \n", syscall(SYS_gettid));
pthread_cond_wait(&cond_var,&sthread_mutex); //wait on condition variable
printf("\n AFTER WAITING ON cond_var with thread: %d \n", syscall(SYS_gettid));
printf("\n got signal for thread: %d \n",syscall(SYS_gettid));
// extract the current request into g local variable
// within the "private stack" of this thread
Req localReq = globalReq;
pthread_mutex_unlock(&sthread_mutex);
printf("\n AFTER UNLOCKING sthread_mutex with thread: %d \n", syscall(SYS_gettid));
// perform the desired task
task(localReq);
printf("\n BEFORE calling sem_post with thread: %d \n", syscall(SYS_gettid));
sem_post(&sem);
} // end while (1)
} // end of worker thread function
生产者线程的代码是:
void *producer(void *arg)
{
while(1)
{
if(front != NULL) // queue not empty
{
// go to sleep if all "worker threads" are occuipied
// or decrement number of free "worker threads" threads by 1
printf(" schedualer thread BEFORE calling sem_wait on sem \n");
sem_wait(&sem);
// lock the sthread mutex in order to "synchronize" with the
// "worker threads"...
printf(" schedualer thread BEFORE locking sthread_mutex \n");
pthread_mutex_lock(&sthread_mutex);
printf(" schedualer thread AFTER locking sthread_mutex \n");
globalReq = extract_element(); // this is the global request variable
// notify the worker threads that an "arriving request" needed to
// be taking care of
printf(" schedualer thread BEFORE calling signal on cond_var \n");
// pthread_cond_signal(&cond_var);
pthread_cond_broadcast(&cond_var);
printf(" schedualer thread AFTER calling signal on cond_var \n");
// unlock the smutex
printf(" schedualer thread BEFORE UNLOCKING sthread_mutex \n");
pthread_mutex_unlock(&sthread_mutex);
printf(" schedualer thread AFTER UNLOCKING sthread_mutex \n");
} // queue not empty
else
continue;
} // end while (1)
} // end of producer
另一个问题:
生产者线程在全局信号量上调用sem_wait
(在开头用工作线程数初始化,在这种情况下 3 )为了自己指示有多少工作线程正在处理请求,并且为了完成这个“机制”,工作线程一旦完成处理他们“赢了”的请求(当争用条件变量时),就调用{ {1}}表示“另一个工作线程可用”
3)这是实现这种“发信号通知有多少可用工作线程”的正确(好/有效)方式吗?
4)有什么好处&通过// *段落中提到的生产者和工作线程之间共享的全局变量“传递”请求的缺点?传递它是一种明智的方式,或者最好“仅”创建一个“新的请求变量”(在使用malloc的堆上),它将“专用”每个工作线程&请求(并且在完成为请求提供服务后,还在每个工作线程中释放它)?
5)请随意向我表明您可能会想到这段代码的任何其他评论(好的或坏的)。
修改
嘿,大家好,
一些额外问题:
除了生产者和工作者线程之外,还有另一个THREAD,称为 listener ,只有TASK才能插入到达链表(FIFO队列)的请求,所以它实际上并不是前面提到的制片人的任务。
所以我的新问题是:
8)关于我的程序的其他信息,我用信号量组成的“信号机制”是否有效?
9)生产者和监听器线程管理的链表有两个全局指针sem_post
和front
分别指向链表的头部和尾部(列表的头部是第一个)请求处理。)
下面是监听器线程执行的插入函数的实现,以及生产者线程执行的“提取”功能。
为了在“队列”(链接列表)上同步这两个线程,我使用了一个名为qmutex的共享互斥锁。
我的问题是,关于下面的两个代码,在每个函数中“放置”互斥锁(锁定和解锁)的“最佳”位置在哪里?
感谢分配,
盖。
插入功能:
rear
提取功能:
void insertion(void *toInsert)
{
struct getInfo *req = (struct getInfo *)toInsert;
newNode = (N*)malloc(sizeof(N));
newNode->req = req;
newNode->next = NULL;
// WHERE SHULD I LOCK (AND UNLOCK) THE QUEUE MUTEX ????????????????????????
if(front == NULL)
{
front = newNode;
printf("empty list - insert as head \n");
}
else
{
rear->next = newNode;
printf(" NOT AN EMPTY list - insert as last node \n");
}
rear = newNode;
} // end of insertion
答案 0 :(得分:5)
不是直接回答您的问题,首先,这里是对典型方法的描述:
您有一种队列或列表,您可以在其中添加工作数据。无论何时添加一组工作数据,首先要锁定互斥锁,添加数据,发出条件变量信号,然后解锁互斥锁。
然后,您的工作线程会锁定互斥锁,并在队列为空时等待循环中的条件。发送信号时,一个或多个工作人员将被唤醒,但只有一个(一次)将获取互斥锁。锁定互斥锁后,“获胜者”会检查队列中是否存在某些内容,将其解压缩,解锁互斥锁,并执行必要的工作。在解锁互斥锁之后,其他线程也可能会被唤醒(并且如果条件被广播将会唤醒),并且将从队列中提取下一部分工作,或者如果队列为空则返回等待。
在代码中,它看起来有点像这样:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#define WORKER_COUNT 3
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_t workers[WORKER_COUNT];
static int queueSize = 0;
static void *workerFunc(void *arg)
{
printf("Starting worker %d\n", (int)arg);
while(1) {
pthread_mutex_lock(&mutex);
while(queueSize < 1) {
pthread_cond_wait(&cond, &mutex);
}
printf("Worker %d woke up, processing queue #%d\n", (int)arg, queueSize);
//Extract work from queue
--queueSize;
pthread_mutex_unlock(&mutex);
//Do work
sleep(1);
}
}
int main()
{
int i;
pthread_mutex_init(&mutex, 0);
pthread_cond_init(&cond, 0);
for(i=0; i<WORKER_COUNT; ++i) {
pthread_create(&(workers[i]), 0, workerFunc, (void*)(i+1));
}
sleep(1);
pthread_mutex_lock(&mutex);
//Add work to queue
queueSize = 5;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
sleep(10);
return 0;
}
(我在线程之后省略了清理工作,并且将工作人员号码传递给线程很快而且很脏,但在这种情况下有效。)
在这里,工作人员将被pthread_cond_broadcast()
唤醒,并且只要队列中存在某些东西就会运行(直到queueSize
返回0 - 想象还有一个实际的队列),然后回去等。
回到问题:
1:互斥锁和保护变量(这里是queueSize
)负责处理这个问题。您还需要保护变量,因为其他原因也会使您的线程 被唤醒(所谓的虚假唤醒,请参阅http://linux.die.net/man/3/pthread_cond_wait)。
2:如果你打电话给pthread_mutex_lock()
,那么被唤醒的线程就像任何其他线程一样攻击互斥锁。
3:我不确定为什么你需要向生产者发出可用工作线程数量的信号?
4:队列需要可以从你的生产者和消费者那里访问 - 但是仍然可以用各种方式用函数(或类,如果你使用的是C ++)封装。
5:我希望上述内容足够吗?
6:pthread_cond_wait()
的事情是它可能有虚假的唤醒。也就是说,即使你没有发出信号,它也可能会醒来。因此,您需要一个保护变量(我的代码示例中while()
周围的pthread_cond_wait()
循环),以确保实际上 是唤醒的理由,{{{ 1}}返回。然后使用与条件使用的相同的互斥锁保护保护变量(以及您需要提取的任何工作数据),然后您可以确定只有一个线程可以完成每项工作。
7:我不是让生产者进入睡眠状态,而是让它添加它可以提取到工作队列的任何数据。如果队列已满,那么它应该进入休眠状态,否则它应该继续添加内容。
8:使用你的Listener线程,我真的不明白为什么你甚至需要你的Producer线程。为什么不让工人自己打电话给pthread_cond_wait()
?
9:您需要保护对列表变量的所有访问。也就是说,在extract_element()
中,在您首次访问insertion()
之前锁定互斥锁,并在上次访问front
后将其解锁。同样的事情在rear
中 - 虽然你需要重写函数,以便在队列为空时也有一个有效的返回值。
答案 1 :(得分:1)
想要确定上一期和其他事项,所以新问题是:
1)如果我仍然想“坚持”我的线程实现,意思是在我写的时候使用互斥锁和条件变量,我怎么能确定当生产者线程只调用pthread_cond_signal时,一个线程将继续run(从pthread_cond_wait之后的指令开始)?
我是否需要添加另一个检查或使用另一个变量,或者(我更喜欢)只使用pthread_cond_wait / signal的通用机制?
注意:我使用pthread_cond_broadcast来“模拟”pthread_cond_signal解除阻塞多个线程的情况。
2)澄清我对该计划的“逻辑”:
生成器线程在分配请求时递减信号量的原因,相反,工作线程增加它的值,是为了使生成器线程在信号量上“进入睡眠状态”,如果所有工作线程都忙 - - &GT;意思是“等待提取和分配过程”,直到(至少)其中一个工作线程可用于处理请求。 这是我想要实现的一个很好的实现,还是有更好的方法呢?
再次感谢分配,
盖。