thepoynt
thepoynt

Reputation: 43

C++ pthreads/semaphores not behaving

I'm new to threading in C++, and I'm sure it shows in my question here.

I'm working on a project for school, and I need to model a 4-way traffic intersection, where cars are threads. I have a clock counter, and every clock tick I generate a random number of cars, creating a simple Car object for each one, put that new car in one of 4 queues (streets) and then make a pthread to handle the car. Each car must wait until it gets to the front of its street, then wait for a semaphore that signals that a car can go. So if there's multiple cars waiting at the intersection, there's not any order as to which goes first.

I'm having issues, however. According to my printouts, a thread will run, exit, but then it will go back to waiting for the semaphore again. This segfaults when it gets the semaphore and tries to do stuff with the car. Any pointers in the right direction would be awesome!

A sample output is:

Running with 10 cars
  |    |    |  
2: Generating 3 cars. Already scheduled: 0, finished: 0
2:  Generating car 2-0 at street 3
2:  Generating car 2-1 at street 3
2:  Generating car 2-2 at street 0
waiting for clk in main
2-2 waiting for turn
2: Car 2-2 driving from street 0
waiting for streets in departure
2-2  |    |    |  2-0, 2-1
  |    |    |  2-0, 2-1
releasing streets in departure
2-2 releasing clock
2-2 exiting
got clk in main
  |    |    |  2-0, 2-1
4: Generating 3 cars. Already scheduled: 3, finished: 1
2-22-2 waiting for turn
4:  Generating car 4-0 at street 1
 waiting for turn
4:  Generating car 4-1 at street 1
4:  Generating car 4-2 at street 2
4-2 waiting for turn
waiting for clk in main
4: Car 2-2 driving from street 0
waiting for streets in departure
  |  4-0, 4-1  |  4-2  |  2-0, 2-1
Segmentation fault (core dumped)

My main method:

int numCars = 0;
deque<deque<Car> > streets; //each queue for each street
bool empty = true; // intersection is empty (Boolean)
int clk = 0; // clock counter
int carsFinished = 0;
int carsScheduled = 0;
sem_t turn;
sem_t streetSem;
sem_t sigSem;
sem_t clkSem;


int main () {
   deque<Car> street0, street1, street2, street3;
   streets.push_back(street0);
   streets.push_back(street1);
   streets.push_back(street2);
   streets.push_back(street3);

   printf("Please enter the number of cars to run:  ");
   cin >> numCars;
   cout << "\nRunning with " << numCars << " cars\n";

   sem_init(&turn, 0, 0);
   sem_init(&streetSem, 0, 1);
   sem_init(&sigSem, 0, 0);
   sem_init(&clkSem, 0, 0);

   while (carsFinished < numCars) {

      // Generate a random number of cars, from 0 to 5
      double r = rndom();
      int numCarsThisRound;
      if (r == 1.0) {
         numCarsThisRound = 5; // so it won't go to 6
      } else {
         numCarsThisRound = r * 6.0;
      }
      int i = 0;

      if ((carsScheduled < numCars) && ((carsScheduled - carsFinished) < 110) && (i < numCarsThisRound)) {
         sem_wait(&streetSem);
         print_streets();
         sem_post(&streetSem);
         cout << clk << ": Generating " << numCarsThisRound << " cars. Already scheduled: " << carsScheduled << ", finished: " << carsFinished << "\n";
      }

      //       there are still cars      there aren't more than 110 cars waiting    we don't go over the number of cars for this clock
      while ((carsScheduled < numCars) && ((carsScheduled - carsFinished) < 110) && (i < numCarsThisRound)) {

         // assign each car to a random street
         double r = rndom();
         int num;
         if (r == 1.0) {
            num = 3; // so it won't go to 4
         } else {
            num = r * 4.0;
         }

         // make new car
         Car car;
         car.setQueue(num);
         std::ostringstream s;
         s << clk << "-" << i;
         std::string id(s.str());
         car.setId(id);

         cout << clk << ":\tGenerating car " << car.id << " at street " << num << "\n";

         sem_wait(&streetSem);
            // if this is the first car in a street, let it know
            if (streets[num].empty())
               car.isFront = true;
            streets[num].push_back(car);
         sem_post(&streetSem);

         // make new thread for that car
         pthread_t pt;
         pthread_create(&pt, NULL, &arrival, (void *)&car);

         carsScheduled++;
         i++;
      }

      // if there's a car that should go this clk
      if (!streetsAreEmpty()) {
         // wait until that car runs before incrementing clk
         sem_post(&turn);
         cout << "waiting for clk in main\n";
         sem_wait(&clkSem);
         cout << "got clk in main\n";
      }
      clk++;

   }

   return (0);
}

Other methods the threads use:

// car arrives at street
void* arrival(void *v) {
   Car car = *(Car*)v;

      // block until I'm at the front of the queue
      while (true) {
         if (car.isFront) {
            break;
         }
      }
      cout << car.id << " waiting for turn\n";
      sem_wait(&turn); // wait for it to be someone's turn
         cout << car.id << " waiting for signal\n";
            cout << clk << ": Car " << car.id << " driving from street " << car.queue << "\n";
            drive();
            departure(car.queue);
            cout << car.id << " releasing clock\n";
         sem_post(&clkSem); // let the main method know I'm done


   cout << car.id << " exiting\n";
   pthread_exit(NULL);
}


// departure of car from intersection
void departure(int i) {
   // one car departs
   cout << "waiting for streets in departure\n";
   sem_wait(&streetSem);
      print_streets();
      streets[i].pop_front();
      if (!streets[i].empty()) {
         streets[i].front().isFront = true;
      }
      print_streets();
   cout << "releasing streets in departure\n";
   sem_post(&streetSem);

   carsFinished++;
}

// take up a clock tick to simulate driving
void drive() {
   clk++;
}

The Car class header - very simple:

#ifndef CAR_H
#define CAR_H

class Car {
    #include <string>

    public:
        Car();
        Car(int);
        void setQueue(int);
        void setId(std::string);
        std::string id;
        int queue;
        bool isFront;
};

#endif

Thanks!

Upvotes: 1

Views: 978

Answers (1)

Karoly Horvath
Karoly Horvath

Reputation: 96258

This is undefined behaviour.

Car car is a local variable and it gets destroyed once it falls out of scope.

You're assuming that a copy is made (Car car = *(Car*)v;) before that happens, but that isn't necessarily true.

Change the life-time of the car, there should be (a heap allocated?) one per thread.

Upvotes: 1

Related Questions