简体   繁体   中英

Pthreads - Producer and consumer with condition variable and mutex - join error and strange cout

I am making multithread program using pthreads. The idea is simple:

  • Car (thread)
  • Petrol supply (thread)
  • Petrol station (resource)

Both car and petrol station have some fuel capacity and after car is out of fuel needs to visit petrol station. After petrol station is out of fuel, petrol supply thread runs and refills resource. Everything seems to be fine excluding that I have to use pthread_exit instead of pthread_join to wait for threads in main function and sometimes double cout for the same car occurs: "-----End of fuel-----" . Am I doing it right?

Structs and some global variables:

#define initialFuel 100
#define loop 10

pthread_mutex_t mutex1, mutex2;
pthread_cond_t isempty;
PetrolDistributor petrolDistributor;

struct Car {
    int capacity = 10;
    int petrol = 5;
};

struct PetrolDistributor {
    int petrol = initialFuel;
    bool isEmpty = false;
};

Threads:

void * threadSupply(void *arg )
{
    for(int i = 0; i<loop; i++) 
{
        pthread_mutex_lock(&mutex1);

        while(!petrolDistributor.isEmpty)
        {
            pthread_cond_wait(&isempty, &mutex1);   //When signal received, do below:
            usleep(2000000);
            petrolDistributor.petrol = initialFuel; //Refill petrol and change state
            petrolDistributor.isEmpty = false;
        }
        pthread_mutex_unlock(&mutex1);
    }
}

void * threadPetrolDriver(void *arg )
{
    Car *car;
    car = (Car*) arg;

    for(int i = 0; i<loop; i++) 
    {
        while(car->petrol > 0) // Car consumes petrol here
        {
            usleep(200000);
            cout << car->petrol << endl;
            car->petrol -= 1;
        }
        cout << "-----End of fuel-----" << "\t\t #" << i << endl;

        pthread_mutex_lock(&mutex1);
        if (petrolDistributor.petrol >= 30)  // If distributor almost empty?
        {
            petrolDistributor.petrol -= car->capacity;  //Substract car's capacity amount of fuel from distributor
            car->petrol = car->capacity;               //Fillup mentioned capacity in car
        }
        else
        {
            petrolDistributor.isEmpty = true;
            pthread_cond_signal(&isempty);
        }
        pthread_mutex_unlock(&mutex1);
    }
}

Main:

int main()
{    
    pthread_t car;
    pthread_t supply;

    Car carPetrol;

    pthread_cond_init(&isempty, NULL);
    pthread_mutex_init(&mutex1, NULL);

    pthread_create(&car, NULL, threadPetrolDriver, (void*) (&carPetrol));
    pthread_create(&supply, NULL, threadSupply, NULL);

   // pthread_join(&car, NULL);     //results error: invalid conversion from ‘pthread_t* {aka long unsigned int*}’ to ‘pthread_t {aka long unsigned int}’ [-fpermissive]|
   // pthread_join(&supply, NULL);
    pthread_exit(NULL);

    return 0;
}

Output example:

-----End of fuel-----    #0
9
(...)
2
1
-----End of fuel-----    #1
-----End of fuel-----    #2  //Also for loop increments
10
9
(...)
3
2
1
-----End of fuel-----    #3
10
9
(...)

And the question is why does output looks like that? Sometimes five iterations are fine and sixth shows double message. And what is wrong with join? Thanks for advices.

The car can show "End of fuel" multiple times because if it finds the petrolDistributor is nearly empty it doesn't wait - it goes around outer the while loop again, without refuelling itself.

What you should be doing here is having the car wait on a condition variable, if it finds the petrolDistributor with insufficient fuel to for the car to refuel. It shouldn't proceed until the petrolDistributor has been refuelled:

pthread_mutex_lock(&mutex1);
if (petrolDistributor.petrol < 30)  // If distributor almost empty?
    pthread_cond_signal(&isempty);

// wait for sufficient fuel to be available
while (petrolDistributor.petrol < car->capacity)
    pthread_cond_wait(&carwaiting, &mutex1);

// refuel
petrolDistributor.petrol -= car->capacity;  //Substract car's capacity amount of fuel from distributor
car->petrol = car->capacity;               //Fillup mentioned capacity in car
pthread_mutex_unlock(&mutex1);

I've used a new conditiona variable carwaiting here. Note that the petrolDistributor.isEmpty boolean is unnecessary - that predicate is just petrolDistributor.petrol < 30 .

The petrolDistributor needs to signal any waiting cars after it refills the station:

    pthread_mutex_lock(&mutex1);

    // Wait for petrol to drop below low-water-mark
    while(petrolDistributor.petrol >= 30)
        pthread_cond_wait(&isempty, &mutex1);

    usleep(2000000);
    petrolDistributor.petrol = initialFuel; //Refill petrol and change state
    pthread_cond_broadcast(&carwaiting); // Wake up any cars waiting for fuel
    pthread_mutex_unlock(&mutex1);

Note I've used pthread_cond_broadcast() here, because if you extend this to include multiple cars, more than one might be waiting for fuel.

Your pthread_join() calls should just be:

pthread_join(car, NULL);
pthread_join(supply, NULL);

( pthread_join() takes a pthread_t as argument, not pthread_t * ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM