线程在C:生产者消费者永远运行

时间:2018-02-01 05:50:25

标签: c multithreading pthreads producer-consumer

我对线程概念不熟悉。 我在C中做生产者消费者问题,但消费者线程与生产者并行时不会运行。

我的代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

int S;
int E;
int F;

void waitS(){
    //printf("hbasd");
    while(S<=0);
    S--;
}

void signalS(){
    S++;
}

void waitE(){
    while(E<=0);
    E--;
}

void signalE(){
    E++;
}
void waitF(){
    while(F<=0);
    F--;
}

void signalF(){
    F++;
}
int p,c;

void* producer(void *n){
    int *j = (int *)n;
    int i = *j;
    while(1){
        waitS();
        waitE();
        printf("Producer %d\n",E);
        signalS();
        signalF();
        p++;
        if(p>=i){
            printf("Exiting: producer\n");
            pthread_exit(0);
        }
    }
}

void* consumer(void *n){
    int *j = (int *)n;
    int i = *j;
    while(1){
        waitS();
        waitF();
        printf("Consumer %d\n",E);
        signalS();
        signalE();
        c++;
        if(c>=i){
            printf("Exiting Consumer\n");
            pthread_exit(0);
        }
    }
}


int main(int argc, char* argv[]){

    int n = atoi(argv[1]);

    E = n;
    S = 1;
    F = 0;

    int pro = atoi(argv[2]);
    int con = atoi(argv[3]);


    pthread_t pid, cid;
    pthread_attr_t attr;

    pthread_attr_init(&attr);

    pthread_create(&pid,&attr,producer,(void *)&pro);
    pthread_create(&cid,&attr,consumer,(void *)&con);

    pthread_join(pid,NULL);
    pthread_join(cid,NULL);

}

当我输入为./a.out 3 4 3时 即n = 3,pro = 4,con = 3

我没有得到任何一种死锁的情况。

我希望输出像

制片人2 制片人1 制片人0 消费者0 消费者1 制片人0 退出:生产者 消费者0 退出:消费者

......类似的输出,其中Producer运行4次,消费者运行三次

当我输入像./a.out 4 4 3这样的输入时 我得到以下输出

制片人3 制片人2 制片人1 制片人0 退出:生产者 消费者0 消费者1 消费者2 退出:消费者

从结果中我得出pthread生产者正在执行第一个然后是pthread消费者的结论。

我希望它们同时执行,以便在给出3 4 3等测试用例时得到类似于第一个预期输出的答案。

4 个答案:

答案 0 :(得分:2)

您正在从不同的线程访问非原子变量而没有任何同步;这是一种竞争条件,它会导致不确定的行为。

特别是,现代CPU为每个CPU内核提供单独的寄存器和单独的高速缓存,这意味着如果在CPU内核#1上运行的线程修改了变量的值,那么该修改可能仅保留在CPU#1&#39;缓存很长一段时间,没有得到&#34;推出&#34;到RAM,所以在CPU核心#2上运行的另一个线程可能不会#34;参见&#34;线程#1更新了很长时间(或许永远不会)。

解决此问题的传统方法是使用一个或多个互斥锁序列化对共享变量的访问(请参阅pthread_mutex_init()pthread_mutex_lock()pthread_mutex_unlock()等),或使用您希望同时从多个线程访问的值的原子变量而不是标准整数。这两种机制都有保护措施,以确保不会发生未定义的行为(如果您正确使用它们)。

答案 1 :(得分:2)

如果没有同步,则无法从两个不同的线程访问相同的内存。 pthreads的标准非常清楚here

  

应用程序应确保限制多个控制线程(线程或进程)对任何内存位置的访问,以便没有控制线程可以读取或修改内存位置,而另一个控制线程可能正在修改它。使用同步线程执行的函数以及相对于其他线程同步内存来限制此类访问。

此外,即使我们忽略了许多CPU不同步内存,除非你明确要求它们,你的代码在正常的C中仍然是不正确的,因为如果变量可以在你的背后改变它们应该是易失性的。但即使volatile可能对某些CPU有帮助,但对于pthreads来说也是不正确的。

只需使用正确的锁定,不要旋转全局变量,有一些方法可以加热房间,比使用CPU便宜得多。

答案 2 :(得分:1)

一般情况下,您应该使用同步原语,但与其他回答者不同,我相信如果我们在 x86架构上运行此程序并且阻止编译器优化代码中的一些关键部分。

According to Wikipedia,x86架构具有几乎连续的一致性,这足以实现生产者 - 消费者算法。

成功实施此类生产者 - 消费者算法的规则非常简单:

  1. 我们必须避免从不同的线程写入相同的变量,即如果一个线程写入变量X,另一个线程只读取X
  2. 我们必须明确地告诉编译器我们的变量可能会在某处发生变化,即在线程变量之间的所有共享上使用volatile关键字。
  3. 这是基于您的代码的工作示例。生产者生成从5到0的数字,消费者消费它们。请记住,这只适用于x86,因为其他架构的排序较弱:

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    volatile int P = 0;
    volatile int C = 0;
    volatile int value = 0;
    
    void produce(int v)
    {
        value = v;
        P++;
    }
    int consume()
    {
        int v = value;
        C++;
        return v;
    }
    
    void waitForConsumer()
    {
        while (C != P)
            ;
    }
    
    void waitForProducer()
    {
        while (C == P)
            ;
    }
    
    
    void *producer(void *n)
    {
        int i = *(int *)n;
        while (1) {
            waitForConsumer();
            printf("Producing %d\n", i);
            produce(i);
            i--;
            if (i < 0) {
                printf("Exiting: producer\n");
                pthread_exit(0);
            }
        }
    }
    
    void *consumer(void *n)
    {
        while (1) {
            waitForProducer();
            int v = consume();
            printf("Consumed %d\n", v);
            if (v == 0) {
                printf("Exiting: consumer\n");
                pthread_exit(0);
            }
        }
    }
    
    int main(int argc, char *argv[])
    {
    
        int pro = 5;
    
        pthread_t pid, cid;
        pthread_attr_t attr;
    
        pthread_attr_init(&attr);
    
        pthread_create(&pid, &attr, producer, (void *)&pro);
        pthread_create(&cid, &attr, consumer, NULL);
    
        pthread_join(pid, NULL);
        pthread_join(cid, NULL);
    }
    

    产生以下结果:

    $ ./a.out
    Producing 5
    Producing 4
    Consumed 5
    Consumed 4
    Producing 3
    Producing 2
    Consumed 3
    Consumed 2
    Producing 1
    Producing 0
    Exiting: producer
    Consumed 1
    Consumed 0
    Exiting: consumer
    

    有关更多信息,我真的推荐Herb Sutter的演讲名为atomic<> Weapons,该演讲时间很长,但您需要了解有关排序和原子的所有信息。

    尽管上面列出的代码在x86上运行正常,但我真的鼓励你观看上面的演示并使用内置原子,如__atomic_load_n(),它将在任何平台上生成正确的汇编代码。

答案 3 :(得分:0)

为生产者和消费者创建新线程,即所有生产者和消费者都有自己的线程。