消费只有在阵列已满时才能启动

问题描述 投票:1回答:2

就像标题说的那样,使用者线程一直等到整个数组被填满,直到开始使用为止,然后生产者一直等到它再次为空,然后将它们绕成一个圈,直到完成为止,我遇到了一个问题与他们的循环。我不知道他们为什么要这么做。保持谦虚,因为这对我来说是一个新话题,我正在尝试了解互斥量和条件。

#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;

}
c multithreading pthreads producer-consumer
2个回答
0
投票

生产者线程和使用者线程交替运行BUFFER_SIZE次的观点是不正确的。这里的程序表现出不确定性,因此生产者和消费者之间可能有许多订购之一。程序的编写方式仅保证两件事:

  1. 如果使用者在缓冲区为空时获得锁,它将放弃锁并等待生产者发出信号。
  2. 如果生产者在缓冲区已满时获得锁,它将放弃锁并等待消费者发出信号。

由于这两个属性,可以保证生产者将始终首先发出,而消费者将始终是要打印的两个线程中的最后一个。它还可以确保两个线程都不能成功连续超过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


0
投票

您应考虑互斥锁是否是您真正想要的同步原语。您可能会考虑的一些替代方法是:

  • 具有两个缓冲区,一个被读取,一个被写入。生产者线程始终具有可用的缓冲区来更新。当两个线程用完它们当前拥有的缓冲区后,它们会在屏障上等待,然后交换正在读取的缓冲区和正在写入的缓冲区。
  • writer线程管理缓冲区。它通过更新指向缓冲区的指针来通知读取器线程,该数据已在新缓冲区中准备好(使用在获取-消耗内存排序中的原子比较和交换)。读取器线程通过清除缓冲区指针通知写入器已准备好接收更多数据,从而使写入器的CAS操作成功。 (如果有两个以上的线程,则这种简单的机制将不再起作用,并且需要单独的读取器计数,以及一些额外的标记位,以防止在编写器重新使用缓冲区时出现A-B-A错误。)
  • 有一个原子元素的循环缓冲区,可以单独访问,并且读写器会更新它们在共享内存中的当前位置。读取器线程不读取其他线程已写入的内容,而写入器线程不写入其他线程已读取的内容。如果任何一个赶上另一个,它就会等待。读写是无锁的。内存防护栏确保一致性。
  • 编写器线程将其写入的每个块添加到读取器线程消耗的免等待缓冲区列表中。
© www.soinside.com 2019 - 2024. All rights reserved.