就像标题说的那样,使用者线程一直等到整个数组被填满,直到开始使用为止,然后生产者一直等到它再次为空,然后将它们绕成一个圈,直到完成为止,我遇到了一个问题与他们的循环。我不知道他们为什么要这么做。保持谦虚,因为这对我来说是一个新话题,我正在尝试了解互斥量和条件。
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 6
#define LOOPS 40
char buff[BUFFER_SIZE];
pthread_mutex_t buffLock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t emptyCond=PTHREAD_COND_INITIALIZER, fullCond=PTHREAD_COND_INITIALIZER;
int buffIndex=0;
void* Producer(){
int i=0;
for(i=0;i<LOOPS;i++){
pthread_mutex_lock(&buffLock);
while(buffIndex==BUFFER_SIZE)
pthread_cond_wait(&fullCond, &buffLock);
buff[buffIndex++]=i;
printf("Producer made: %d\n", i);
pthread_mutex_unlock(&buffLock);
pthread_cond_signal(&emptyCond);
}
pthread_exit(0);
}
void* Consumer(){
int j=0, value=0;
for(j=0;j<LOOPS;j++){
pthread_mutex_lock(&buffLock);
while(buffIndex==0)
pthread_cond_wait(&emptyCond, &buffLock);
value=buff[--buffIndex];
printf("Consumer used: %d\n", value);
pthread_mutex_unlock(&buffLock);
pthread_cond_signal(&fullCond);
}
pthread_exit(0);
}
int main(){
pthread_t prodThread, consThread;
pthread_create(&prodThread, NULL, Producer, NULL);
pthread_create(&consThread, NULL, Consumer, NULL);
pthread_join(prodThread, NULL);
printf("Producer finished.\n");
pthread_join(consThread, NULL);
printf("Consumer finished.\n");
return 0;
}
生产者线程和使用者线程交替运行BUFFER_SIZE
次的观点是不正确的。这里的程序表现出不确定性,因此生产者和消费者之间可能有许多订购之一。程序的编写方式仅保证两件事:
由于这两个属性,可以保证生产者将始终首先发出,而消费者将始终是要打印的两个线程中的最后一个。它还可以确保两个线程都不能成功连续超过BUFFER_SIZE
次请求锁。
除了以上两个保证之外,实际运行将产生完全基于节点的结果。碰巧的是,您的操作系统已任意决定在观察到的运行中重复地重新计划最新的线程。这是合理的,因为您的程序已对调度程序说“在上述两个规则的排序约束内做任何您想做的事”。如果需要,OS可以随意偏向scheduling同一线程以再次在CPU上运行;实际上,这可能是最有意义的,因为刚刚在CPU上运行的线程已经加载了资源(locality of reference),因此减少了context switches的开销。
虽然OS可能会重复调度同一线程,但也可能会例如对整个进程进行调度并运行更高优先级的进程,从而有可能驱逐第一个进程的working set。在这种情况下,当重新调度第一个进程时,任何线程都可能具有被调度的效果。
在任何情况下,只要程序员允许不确定性,出于各种复杂的原因,订购就不易使用,may not appear to be random也就不可用。
正如我在评论中提到的,重要的是能够在不运行程序的情况下使自己确信程序的排序属性。运行程序可以证明存在不确定性,但是不能证明程序具有确定性。一些多线程程序包含细微的调度错误或竞争条件,它们可能仅在一万亿次(或更多!)运行中出现一次,因此无法手动检查此类不确定性程序。幸运的是,这很简单,因此很容易将其运行直到不确定性出现为止。
sleep(1)
中的sleep(1)
是调试多线程程序的有用工具。此函数导致调度调用线程,从而扰乱程序的自然顺序并强制执行某种顺序。这可以帮助您证明订购属性。例如,在unistd.h
之后添加sleep(1)
表示,只要有机会,消费者就可以在程序中进行pthread_cond_signal(&emptyCond);
生产之前抢到锁。
对于复杂程序,存在诸如BUFFER_SIZE
之类的工具,可以以编程方式插入Cuzz调用以发现有序错误。有关各种资源和策略,请参见sleep
。
您应考虑互斥锁是否是您真正想要的同步原语。您可能会考虑的一些替代方法是: