Skip to main content
Engineering LibreTexts

10.3: Mutual exclusion

  • Page ID
    40636
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    We can make the queue thread safe with a mutex. This version of the code is in queue_mutex.c.

    First we add a Mutex pointer to the queue structure:

    typedef struct {
        int *array;
        int length;
        int next_in;
        int next_out;
        Mutex *mutex;          //-- this line is new
    } Queue;
    

    And initialize the Mutex in make_queue:

    Queue *make_queue(int length) {
        Queue *queue = (Queue *) malloc(sizeof(Queue));
        queue->length = length;
        queue->array = (int *) malloc(length * sizeof(int));
        queue->next_in = 0;
        queue->next_out = 0;
        queue->mutex = make_mutex();   //-- new
        return queue;
    }
    

    Next we add synchronization code to queue_push:

    void queue_push(Queue *queue, int item) {
        mutex_lock(queue->mutex);   //-- new
        if (queue_full(queue)) {
          mutex_unlock(queue->mutex);   //-- new
          perror_exit("queue is full");
        }
      
        queue->array[queue->next_in] = item;
        queue->next_in = queue_incr(queue, queue->next_in);
        mutex_unlock(queue->mutex);   //-- new
    }
    

    Before checking whether the queue is full, we have to lock the Mutex. If the queue is full, we have to unlock the Mutex before exiting; otherwise the thread would leave it locked and no other threads could proceed.

    The synchronization code for queue_pop is similar:

    int queue_pop(Queue *queue) {
        mutex_lock(queue->mutex);
        if (queue_empty(queue)) {
          mutex_unlock(queue->mutex);
          perror_exit("queue is empty");
        }
      
        int item = queue->array[queue->next_out];
        queue->next_out = queue_incr(queue, queue->next_out);
        mutex_unlock(queue->mutex);
        return item;
    }
    

    Note that the other Queue functions, queue_full, queue_empty, and queue_incr do not try to lock the mutex. Any thread that calls these functions is required to lock the mutex first; this requirement is part of the documented interface for these functions.

    With this additional code, the queue is thread safe; if you run it, you should not see any synchronization errors. But it is likely that the consumer will exit at some point because the queue is empty, or the producer will exit because the queue is full, or both.

    The next step is to add condition variables.


    This page titled 10.3: Mutual exclusion is shared under a CC BY-NC license and was authored, remixed, and/or curated by Allen B. Downey (Green Tea Press) .

    • Was this article helpful?