简体   繁体   中英

why dosen't sem_wait() function block even when sem_t value is zero?

I'm trying to implement a simple solution for Dining philosophers problem (with five philosophers) and my solution is based on this logic:

sem_t S[philosophers_number]
for each philosopher
{
    while(TRUE)
    {
        if(current philosopher number != last philosopher)
        {
            thinking()

            //i is number of current philosopher
            sem_wait(take_chopstick(S[(i+1) % philosophers_number])) // right chopstick
            sem_wait(take_chopstick(S[i])) // left chopstick

            eat()

            sem_post(put_chopstick(S[(i+1) % philosophers_number]))
            sem_post(put_chopstick(S[i]))
        }
        else
        {
            thinking()

            //i is number of current philosopher
            sem_wait(take_chopstick(S[i])) // left chopstick
            sem_wait(take_chopstick(S[(i+1) % philosophers_number])) // right chopstick

            eat()

            sem_post(put_chopstick(S[i]))          
            sem_post(put_chopstick(S[(i+1) % philosophers_number]))  
        }
}

Each philosopher first think for less than three seconds

Then if right chopstick is available philosopher will take it, and if also left one is available philosopher will take that too and start eating for less than three seconds

Then philosopher will put down chopsticks and make them available for other philosophers

To avoid cyclic wait, for last philosopher I will first pick left chopstick then right one and go on the same process

Here is the code I implemented based on this logic:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>

#define THREADS 5
sem_t chopstick[THREADS];

void thinking(int ph_num)
{
    printf("philosopher %d is thinking\n", ph_num);
    int t = rand() % 3;
    sleep(t);// up to 3 secs thinking
}

void eat(int ph_num)
{
    printf("philosopher %d is eating\n", ph_num);
    int t = rand() % 3;
    sleep(t);// up to 3 secs eating
}

void *philosopher(void * ph_num )
{
    int num=(int)ph_num;
    while(1)
    {
        if(num < THREADS - 1)
        {
            thinking(num);
            
            //pick up right chopstick
            sem_wait(&chopstick[(num + 1) % THREADS]);

            //to make deadlocks absolutly happen, wait 1 sec then pickup left chopstick
            sleep(1);
            
            //pick up left chopstick
            sem_wait(&chopstick[num]);
        
            eat(num);   

            //put down right chopstick
            sem_post(&chopstick[(num + 1) % THREADS]);

            //put down left chopstick
            sem_post(&chopstick[num]);
        }

        else // last one pick left chopstick first, instead of right one to avoid cyclic wait
        {
                        thinking(num);

                        //pick up left chopstick
                        sem_wait(&chopstick[num]);

                        //to make deadlocks absolutly happen, wait 1 sec then pickup left chopstick 
                        sleep(1); 
                        
                        //pick up right chopstick
                        sem_wait(&chopstick[(num + 1) % THREADS]);

                        eat(num);

                        //put down left chopstick
                        sem_post(&chopstick[num]);

                        //put down right chopstick
                        sem_post(&chopstick[(num + 1) % THREADS]);
        }
    }

    pthread_exit((void *)num);
}

int main ()
{
    for(int i = 0; i < THREADS; i++)
    {
        sem_init(&chopstick[i],0,1);
    }

    pthread_t threads[THREADS];
    
    for(int i = 0; i < THREADS; i++)
        pthread_create(&threads[i], NULL, philosopher, (void *)i);
    
    for(int i = 0; i < THREADS; i++)
        pthread_join(threads[i],NULL);
    return 0;
}

But during debugging this code a problem happened, where chopstick[i] was 0 before sem_wait(&chopstick[num]) instead of blocking current thread, until a chopstick is available sem_wait() carried on, so a philosopher started eating without an actual chopstick.

Can anyone help me figure out where is my problem?

You cannot cast void* to int. Fix this error first.

void *philosopher(void * ph_num )
{
    int num=(*(int*)ph_num);
    ...
}

You are giving an address of loop variable to the threads (philosophers). And all threads (including main thread) uses same i address.

for(int i = 0; i < THREADS; i++)
        //pthread_create(&threads[i], NULL, philosopher, (void *)i); // Wrong cast here too, int cannot be cast to void*
          pthread_create(&threads[i], NULL, philosopher, (void *)&i); // True way.

Possible race condition with thread parameters

After that, there is a possible race condition. You pass the address of the loop variable i to threads as a parameter. So, main thread and other threads(philosophers) operates on the same address. Your philosopher threads can start to run after your main thread creates all threads. This means that the value of the i will stay at NUMBERS value that you define. So, all threads will use same i value, instead using different values of the i(0,1,2,3....).

To be sure about all threads will use different memory location for indexing, allocate a different memory for them:

for(int i = 0; i < THREADS; i++)
{
        int* index_for_thread = (int*)calloc(sizeof(int), 1);
        *index_for_thread = i;
        pthread_create(&threads[i], NULL, philosopher, index_for_thread ); // pass new allocated address to thread.
}

After that, you can safely de-reference the thread parameter, get the value and delete it.

void *philosopher(void * ph_num )
{
    int num=(*(int*)ph_num);
    free(ph_num)// free the address, since you allocated in the main.
    printf("Philosopher thread %d started to run.. \n", num); // Add this line to be sure about all Philosophers started with true id (num).
}

Getting value of semaphores

Yes, sem_get_value() gives you the value of the specified semaphore. Since you are operating on the same semaphore on different threads, getting the value of semaphore may not give you the true value. Debugging, trusting the "values" of the semaphores is not good a idea.

Posix Semaphores on Dining Philosopher based Problems

Posix semaphores is not capable of doing operations the multiple semaphores at the same time . If every philosopher take the right fork, they will wait for the left fork which is not possible in this scenario. Please consider using System V semaphores that allow you to do operations on the multiple semaphores at the same time.

Your implementation is correct, the problem you have is in the method of debugging. If you use gdb , you will be stopped on only one thread, while the rest of the thread will continue the execution, so between the time you have inspected the semaphore and the time you stepped to the next line, other thread will progress the execution and can change the value you had inspected.

To be effective in debugging the threads, you need to assure that only the thread which is currently observed is scheduled and the rest of the threads are blocked. To do so, you need to change the scheduler-locking after you stop on the thread. You can set it to on or step , depending if you want the threads to by fully stopped, or only stopped during the singe-step operations (see help set scheduler-locking for more details).

Once the threads are locked you can use info threads to check what the rest of the threads are doing at the time. You can use thread <<n>> to change to the n-th thread and use where to check the thread stack.

Here is example with the scheduler set to step . You can see that only one thread had progressed on the next command.

(gdb) b 37
Breakpoint 1 at 0x1388: file test003.c, line 37.
(gdb) r
Starting program: /home/jordan/Development/tmptest/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7d90700 (LWP 4002538)]
philosopher 0 is thinking
[New Thread 0x7ffff758f700 (LWP 4002539)]
philosopher 1 is thinking
[New Thread 0x7ffff6d8e700 (LWP 4002540)]
philosopher 2 is thinking
[2] picking 3
[New Thread 0x7ffff658d700 (LWP 4002541)]
[Switching to Thread 0x7ffff6d8e700 (LWP 4002540)]

Thread 4 "a.out" hit Breakpoint 1, philosopher (ph_num=0x2) at test003.c:37
37              sem_wait(&chopstick[(num + 1) % THREADS]);
(gdb) set scheduler-locking step
(gdb) info threads
  Id   Target Id                                   Frame
  1    Thread 0x7ffff7d91740 (LWP 4002534) "a.out" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
  2    Thread 0x7ffff7d90700 (LWP 4002538) "a.out" 0x00007ffff7e743bf in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,
    req=req@entry=0x7ffff7d8fe60, rem=rem@entry=0x7ffff7d8fe60) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
  3    Thread 0x7ffff758f700 (LWP 4002539) "a.out" 0x00007ffff7e743bf in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,
    req=req@entry=0x7ffff758ee60, rem=rem@entry=0x7ffff758ee60) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
* 4    Thread 0x7ffff6d8e700 (LWP 4002540) "a.out" philosopher (ph_num=0x2) at test003.c:37
  5    Thread 0x7ffff658d700 (LWP 4002541) "a.out" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
(gdb) n
38              printf("[%i] picked %i\n", num, (num + 1) % THREADS);
(gdb) info threads
  Id   Target Id                                   Frame
  1    Thread 0x7ffff7d91740 (LWP 4002534) "a.out" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
  2    Thread 0x7ffff7d90700 (LWP 4002538) "a.out" 0x00007ffff7e743bf in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,
    req=req@entry=0x7ffff7d8fe60, rem=rem@entry=0x7ffff7d8fe60) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
  3    Thread 0x7ffff758f700 (LWP 4002539) "a.out" 0x00007ffff7e743bf in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,
    req=req@entry=0x7ffff758ee60, rem=rem@entry=0x7ffff758ee60) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
* 4    Thread 0x7ffff6d8e700 (LWP 4002540) "a.out" philosopher (ph_num=0x2) at test003.c:38
  5    Thread 0x7ffff658d700 (LWP 4002541) "a.out" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78

As you can see, after executing next, I am remaining on the same thread and other threads had not progressed.

I have used modified the code to make more visible what is happening, here is the code I used:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>

#define THREADS 5

sem_t chopstick[THREADS];

void thinking(int ph_num)
{
    printf("philosopher %d is thinking\n", ph_num);
    int t = rand() % 3;
    sleep(t);// up to 3 secs thinking
}

void eat(int ph_num)
{
    printf("philosopher %d is eating\n", ph_num);
    int t = rand() % 3;
    sleep(t);// up to 3 secs eating
}

void *philosopher(void * ph_num )
{
    int num=(int)ph_num;
    while(1)
    {
        if(num < THREADS - 1)
        {
            thinking(num);

            //pick up right chopstick
            printf("[%i] picking %i\n", num, (num + 1) % THREADS);
            sem_wait(&chopstick[(num + 1) % THREADS]);
            printf("[%i] picked %i\n", num, (num + 1) % THREADS);

            //to make deadlocks absolutly happen, wait 1 sec then pickup left chopstick
            //sleep(1);

            //pick up left chopstick
            printf("[%i] picking %i\n", num, num);
            sem_wait(&chopstick[num]);
            printf("[%i] picked %i\n", num, num);

            eat(num);

            //put down right chopstick
            printf("[%i] put %i\n", num, (num + 1) % THREADS);
            sem_post(&chopstick[(num + 1) % THREADS]);

            //put down left chopstick
            printf("[%i] put %i\n", num, num);
            sem_post(&chopstick[num]);
        }

        else // last one pick left chopstick first, instead of right one to avoid cyclic wait
        {
            thinking(num);

            //pick up left chopstick
            printf("[%i] picking %i\n", num, num);
            sem_wait(&chopstick[num]);
            printf("[%i] picked %i\n", num, num);

            //to make deadlocks absolutly happen, wait 1 sec then pickup left chopstick
            //sleep(1);

            //pick up right chopstick
            printf("[%i] picking %i\n", num, num+1);
            sem_wait(&chopstick[(num + 1) % THREADS]);
            printf("[%i] picked %i\n", num, num+1);

            eat(num);

            //put down left chopstick
            printf("[%i] put %i\n", num, num);
            sem_post(&chopstick[num]);

            //put down right chopstick
            printf("[%i] put %i\n", num, (num + 1) % THREADS);
            sem_post(&chopstick[(num + 1) % THREADS]);
        }
    }

    pthread_exit((void *)num);
}

int main ()
{
    for(int i = 0; i < THREADS; i++)
    {
        sem_init(&chopstick[i],0,1);
    }

    pthread_t threads[THREADS];

    for(int i = 0; i < THREADS; i++)
        pthread_create(&threads[i], NULL, philosopher, (void *)i);

    for(int i = 0; i < THREADS; i++)
        pthread_join(threads[i],NULL);
    return 0;
}

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