Linux locks, condition variables, semaphores

Thread synchronization:

Synergetic, sequential access to public area data. Prevents data clutter and time-related errors.

Use of locks:

Protect public data. All threads should acquire locks before accessing public data. However, the lock itself is not mandatory.

mutex mutex

step:

1. pthread_mutex_t type.

2. pthread_mutex_t lock; create a lock

3. pthread_mutex_init; initialization

4. pthread_mutex_lock; lock, mutex– => 1 –> 0

5. Access shared data (stdout)

6. pthrad_mutext_unlock(); unlock, mutex + + => 0 –> 1

7. pthead_mutex_destroy; destroy lock

Initialize the mutex:

pthread_mutex_t mutex;

1. Dynamic initialization: pthread_mutex_init( & amp;mutex, NULL);

2. Static initialization: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

*Precautions:

Try to ensure the granularity of the lock, the smaller the better. (Lock before accessing shared data. Unlock [immediately] after accessing.)

A mutex is essentially a structure. We can see it as an integer. The initial value is 1. (The pthread_mutex_init() function call succeeds.)

Locking: –Operation, blocking thread.

Unlock: + + operation, wake up the thread blocked on the lock.

try lock: try to lock, success –. Fail, return. Also set the error number EBUSY

Read-write lock rwlock

There is only one lock. Lock data in read mode – read lock. Write locks to data – write locks.

Read shared, write exclusive. If the lock has been locked in read mode, then locking in read mode will succeed, and locking in write mode will block.

Write locks have high priority. Read locks and write locks request simultaneous blocking and give priority to satisfying write locks.

Compared with mutex, when there are many reading threads, the access efficiency is improved

pthread_rwlock_t rwlock; define lock

pthread_rwlock_init( & amp;rwlock, NULL); Initialize the lock

pthread_rwlock_rdlock( & amp;rwlock); Lock in the form of reading

pthread_rwlock_wrlock( & amp;rwlock); Lock in the form of writing

pthread_rwlock_unlock( & amp;rwlock); unlock

pthread_rwlock_destroy( & amp;rwlock); Destroy the lock

condition variable cond

Condition variable: It is not a lock itself, but it is usually used in conjunction with a mutex.

Main application functions:

pthread_cond_init(); initialization

pthread_cond_destroy(); destroy

pthread_cond_wait( &cond, &mutex);

effect:

1) Block waiting for the condition variable to be satisfied.

2) Unlock the successfully locked semaphore (equivalent to pthread_mutex_unlock( & amp;mutex)), 12 steps is an atomic operation.

3) When the condition is met and the function returns, unblock and re-apply for the mutex. Relock the semaphore (equivalent to, pthread_mutex_lock( & amp;mutex);).

pthread_cond_timedwait();

pthread_cond_signal(); Wake up (at least) one thread blocked on the condition variable.

pthread_cond_broadcast(); Wake up all threads blocked on the condition variable.

The return value of the above 6 functions is . It returns 0 successfully, and directly returns an error number if it fails.

The pthread_cond_t type is used to define condition variables.

pthread_cond_t cond;

Initialize the condition variable:

Dynamic initialization: pthread_cond_init( & amp;cond, NULL);

Static initialization: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Blocking wait condition:

pthread_cond_wait( &cond, &mutex);

effect:

1) Block waiting for the condition variable to be satisfied.

2) Unlock the successfully locked semaphore (equivalent to pthread_mutex_unlock( & amp;mutex)), 12 steps is an atomic operation.

3) When the condition is met and the function returns, unblock and re-apply for the mutex. Relock the semaphore (equivalent to, pthread_mutex_lock( & amp;mutex);).

Example: One producer with multiple consumers

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

void err_thread(int ret, char *str){
    if (ret != 0) {
        fprintf(stderr, "%s:%s\\
", str, strerror(ret));
        pthread_exit(NULL);
    }
}

struct msg {
    int num;
    struct msg *next;
};

struct msg *head;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // define/initialize a mutex
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; // define/initialize a condition variable

void *producer(void *arg){
    while (1) {
        struct msg *mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1; // Simulate the production of a data`
        printf("--produce %d\\
", mp->num);
        
        pthread_mutex_lock( & amp;mutex); // lock mutex
        mp->next = head; // write public area
        head = mp;
        pthread_mutex_unlock( & amp;mutex); // unlock the mutex
        
        pthread_cond_signal( & amp;has_data); // wake up the thread blocked on the condition variable has_data.
        
        sleep(rand() % 3);
    }
    
    return NULL;
}

void *consumer(void *arg){
    while (1) {
        struct msg *mp;
        
        pthread_mutex_lock( & amp;mutex); // lock mutex
        
        /*
        Both consumers are blocked on the condition variable, which means there is no data to consume. After the work is over, the lock is returned, and the producer hereby
        When a piece of data is produced, two consumers blocked by the condition variable will be woken up at the same time, and the two consumers will grab the lock when they are done. result
        That is, consumer A gets the lock and starts to consume data, while consumer B is blocked on the lock. After A finishes consuming the data, return the lock,
        B is woken up, but at this time there is no data for B to consume. So consumers blocking on condition variables should use
        while loop. In this way, after A consumes the data, the first thing B does is not to acquire the lock, but to determine the condition variable.
        */
        while (head == NULL) {
            pthread_cond_wait( & amp;has_data, & amp;mutex); // block waiting for condition variable, unlock
        } // When pthread_cond_wait returns, rewrite the lock mutex
        
        mp = head;
        head = mp->next;
        
        pthread_mutex_unlock( & amp;mutex); // unlock the mutex
        printf("---------consumer id = %lu :%d\\
", pthread_self(), mp->num);
        
        free(mp);
        sleep(rand()%3);
    }
    
    return NULL;
}

int main(int argc, char *argv[]){
    int ret;
    pthread_t pid, cid;
    
    srand(time(NULL));
    
    ret = pthread_create( & amp;pid, NULL, producer, NULL); // producer
    if (ret != 0)
        err_thread(ret, "pthread_create producer error");
    
    ret = pthread_create( & amp;cid, NULL, consumer, NULL); // consumer
    if (ret != 0)
        err_thread(ret, "pthread_create consumer error");
    
    ret = pthread_create( & amp;cid, NULL, consumer, NULL); // consumer
    if (ret != 0)
        err_thread(ret, "pthread_create consumer error");
    
    ret = pthread_create( & amp;cid, NULL, consumer, NULL); // consumer
    if (ret != 0)
        err_thread(ret, "pthread_create consumer error");
    
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    
    return 0;
}

Semaphore sem

Applied to synchronization between threads and processes.

Equivalent to a mutex initialized to N. The N value indicates the number of threads that can access the shared data area at the same time.

function:

sem_t sem; defines the type.

int sem_init(sem_t *sem, int pshared, unsigned int value);

parameter:

sem: semaphore.

pshared: 0: Used for inter-thread synchronization.

1: Used for inter-process synchronization.

value: N value. (specify the number of threads to access simultaneously)

sem_destroy();

sem_wait(); One call, one — operation, when the value of the semaphore is 0, — will block again.

(compared to pthread_mutex_lock)

sem_post(); One call, one ++ operation. When the value of the semaphore is N, another ++ will block.

(compared to pthread_mutex_unlock)