Search code examples
cmultithreadingpthreadsdeadlockpthread-join

Simple example for pthread_join deadlock


I am looking for a very simple example to demonstrate a deadlock using pthread_join; however, this is not trivial.

I started with this:

void* joinit(void* tid)
{
  pthread_t* tid_c = (pthread_t*)tid;
  int retval = pthread_join(*tid_c, NULL);
  printf("In joinit: tid = %d, retval = %d \n", *tid_c, retval);
  return NULL;
}

int main()
{
  pthread_t thread1;
  pthread_t thread2;

  pthread_create(&thread1, NULL, joinit, &thread2);
  pthread_create(&thread2, NULL, joinit, &thread1);

  pthread_join(thread2, NULL);  

  return 0;
}

But however, it says 'EINVAL' (invalid argument) because thread2 is not yet specified when pthread_create for thread1 is called.

Any ideas?


Solution

  • If you're just wanting to demonstrate that a pthread_join can cause a deadlock, you could do something similar to the following code:

    #include <stdio.h>
    #include <pthread.h>
    
    void* joinit(void* tid)
    {
        printf("In %#x, waiting on %#x\n", pthread_self(), (*((pthread_t*)tid)));
        pthread_join((*((pthread_t*)tid)), NULL);
        printf("Leaving %#x\n", pthread_self());
        return NULL;
    }
    
    int main(void)
    {
        pthread_t thread1 = pthread_self();
        pthread_t thread2;
        pthread_create(&thread2, NULL, joinit, &thread1);
        joinit(&thread2);
        return 0;
    }
    

    This will cause the main thread to wait on the spawned thread and the spawned thread to wait on the main thread (causing a guaranteed deadlock) without the need for extra locking primitives to clutter up what you are trying to demonstrate.

    And to answer some of your questions more directly:

    it says 'EINVAL' (invalid argument) because thread2 is not yet specified when pthread_create for thread1 is called.

    ... and from one of your comments ...

    I tried this and it worked, but the problem is, it only works SOMETIMES because sometimes I get EINVAL again.

    In your code, you call pthread_create consecutively to spawn the 2 threads:

    pthread_create(&thread1, NULL, joinit, &thread2);
    pthread_create(&thread2, NULL, joinit, &thread1);
    

    In your joinit code, you grab the thread handle passed in to join on:

    pthread_t* tid_c = (pthread_t*)tid;
    int retval = pthread_join(*tid_c, NULL);
    

    The reason this sometimes works and others you'll get EINVAL has to do with time slices allocated to each thread's context and sequencing. When the first pthread_create is called you will have a valid handle to thread1 after it returns but the handle to thread2 is not valid yet, at least not until the 2nd pthread_create is called.

    To this, when a thread is created, the act of the thread coming "alive" (i.e. the thread function actually running) could take some extra time even though the thread handle returned is valid. In these instances, there is a chance one thread can execute more code than might be "expected". In your code, both pthread_create functions might happen to have been called in the time slice allocated for the main thread which could give each spawned thread enough "time" before hitting the pthread_join statement allowing tid_c to point to a valid handle; in the EINVAL case, pthread_create(&thread1, NULL, joinit, &thread2) was called and the spawned thread hit the pthread_join(*tid_c, NULL) before pthread_create(&thread2, NULL, joinit, &thread1) could give thread2 a valid handle (causing the error).

    If you wanted to keep your code similar to how it is now, you would need to add a lock of some sort to ensure the threads don't exit or call anything prematurely:

    #include <stdio.h>
    #include <pthread.h>
    
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    
    void* joinit(void* tid)
    {
        /* this can be above the lock because it will be valid after lock is acquired */
        pthread_t* tid_c = (pthread_t*)tid;
        int retval = -1;
        pthread_mutex_lock(&lock);
        pthread_mutex_unlock(&lock);
        printf("%#x waiting on %#x\n", pthread_self(), *tid_c);
        retval = pthread_join(*tid_c, NULL);
        printf("In joinit: tid = %d, retval = %d \n", *tid_c, retval);
        return NULL;
    }
    
    int main()
    {
        pthread_t thread1;
        pthread_t thread2;
        /* get the lock in the main thread FIRST */
        pthread_mutex_lock(&lock);
        pthread_create(&thread1, NULL, joinit, &thread2);
        pthread_create(&thread2, NULL, joinit, &thread1);
        /* by this point, both handles are "joinable", so unlock  */
        pthread_mutex_unlock(&lock);
    
        /* can wait on either thread, but must wait on one so main thread doesn't exit */
        pthread_join(thread2, NULL);
        return 0;
    }
    

    Hope this can help.