Multithreaded programming -- mutex, condition variable

Note: This article is for personal study/review, code reference mutex_哔哩哔哩_bilibili

1. Thread synchronization

When multiple threads access the same shared resource, they need to execute the access sequentially. This method of thread access to memory is called thread synchronization.

There are several ways to achieve thread synchronization:

mutex

condition variable

amount of signal

spin lock

read-write lock

2. Mutex lock

1) Initialize the lock

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutex_attr);

mutex_attr参数选择:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

或者定义互斥锁的时候直接使用下面语句对锁进行初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2) Blocking and locking. If the mutex has been locked by other threads, the caller will block and wait until it is unlocked.

int pthread_mutex_lock(pthread_mutex_t *mutex);

3) Non-blocking lock, if the mutex is not locked, it will be locked, if the mutex is locked, the function will immediately return failure

int pthread_mutex_trylock(pthread_mutex_t *mutex);

4) Unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

5) Destroy locks and release resources

int pthread_mutex_destroy(pthread_mutex_t *mutex);

There is a priority wake-up process for the mutex

The test demo is as follows:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

using std::cout;
using std::endl;

char buffer[100];

pthread_mutex_t mutex;

void *callback(void *arg)
{
        for(int i = 0; i < 3; i++) {
                // cout << time(0) << ":  lock --- pthread " << (long)arg << endl;
                pthread_mutex_lock(&mutex);
                cout << time(0) << ":  lock --- pthread " << (long)arg << endl;

                sprintf(buffer, "%d:%ld,%d", time(0), pthread_self(), i);
                sleep(5);

                pthread_mutex_unlock(&mutex);
                cout << time(0) << ":  unlock ---pthread " << (long)arg << endl;
                
                usleep(100);
        }
}

int main()
{
        pthread_mutex_init(&mutex, NULL);

        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, callback, (void *)1);
        pthread_create(&tid2, NULL, callback, (void *)2);

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        pthread_mutex_destroy(&mutex);

        return 0;
}

The execution results are as follows:

1676601133:  lock --- pthread 1
1676601138:  unlock ---pthread 1
1676601138:  lock --- pthread 2
1676601143:  unlock ---pthread 2
1676601143:  lock --- pthread 1
1676601148:  unlock ---pthread 1
1676601148:  lock --- pthread 2
1676601153:  unlock ---pthread 2
1676601153:  lock --- pthread 1
1676601158:  unlock ---pthread 1
1676601158:  lock --- pthread 2
1676601163:  unlock ---pthread 2

As can be seen from the test log, each thread accesses the global variable buffer one by one

3. Condition variables

Condition variables are used to block a thread until other threads notify it that the condition has been met

Condition variables seem simple, but they are very clever when used with mutexes

1) Initialize the condition variable

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);


或者定义条件变量的时候直接使用下面语句对条件变量进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2) Blocking and waiting

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

3) Waiting for timeout

int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const timespec *abstime);

4) Wake up a thread waiting for the condition

int pthread_cond_signal(pthread_cond_t *cond);

5) Wake up all threads waiting for the condition

int pthread_cond_broadcast(pthread_cond_t *cond);

6) Destroy the condition variable

int pthread_cond_destroy(pthread_cond_t *cond);

Test demo:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

using std::cout;
using std::endl;

pthread_mutex_t mutex;
pthread_cond_t cond;

void *callback1(void *arg)
{
        while(1) {
                pthread_cond_wait(&cond, &mutex);
                cout << "pthread 1 wake up!" << endl;
        }
}

void *callback2(void *arg)
{
        while(1) {
                pthread_cond_wait(&cond, &mutex);
                cout << "pthread 2 wake up!" << endl;
        }
}

void func(int sig)
{
        pthread_cond_signal(&cond);
        // pthread_cond_broadcast(&cond);
}

int main()
{
        signal(15, func);

        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);

        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, callback1, NULL);
        pthread_create(&tid2, NULL, callback2, NULL);

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

        return 0;
}

Test Results:

另外一个终端的输入:
killall -15 test2
killall -15 test2
killall -15 test2
killall -15 test2

输出:
pthread 1 wake up!
pthread 2 wake up!
pthread 1 wake up!
pthread 2 wake up!

If you comment the pthread_cond_signal function and open the comment of the pthread_cond_broadcast function, you can get the following results:

另外一个终端的输入:
killall -15 test2
killall -15 test2
killall -15 test2
killall -15 test2

输出:
pthread 1 wake up!
pthread 2 wake up!
pthread 1 wake up!
pthread 2 wake up!
pthread 1 wake up!
pthread 2 wake up!
pthread 1 wake up!
pthread 2 wake up!

4. The relationship between condition variables and mutex locks

Test demo:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

using std::cout;
using std::endl;

pthread_mutex_t mutex;
pthread_cond_t cond;

void *callback1(void *arg)
{
        while(1) {
                pthread_mutex_lock(&mutex);
                cout << "pthread 1 wait" << endl;
                pthread_cond_wait(&cond, &mutex);
                cout << "pthread 1 wake up!" << endl;
                pthread_mutex_unlock(&mutex);        
        }
}

void *callback2(void *arg)
{
        while(1) {
                pthread_mutex_lock(&mutex);
                cout << "pthread 2 wait" << endl;
                pthread_cond_wait(&cond, &mutex);
                cout << "pthread 2 wake up!" << endl;
                pthread_mutex_unlock(&mutex);        
        }
}

void func(int sig)
{
        pthread_cond_signal(&cond);
        // pthread_cond_broadcast(&cond);
}

int main()
{
        signal(15, func);

        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);

        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, callback1, NULL);
        pthread_create(&tid2, NULL, callback2, NULL);

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

        return 0;
}

The execution results are as follows:

另一个终端:
killall -15 test2
killall -15 test2
killall -15 test2
killall -15 test2

测试结果:
pthread 1 wait
pthread 2 wait
pthread 1 wake up!
pthread 1 wait
pthread 2 wake up!
pthread 2 wait
pthread 1 wake up!
pthread 1 wait
pthread 2 wake up!
pthread 2 wait

What happens inside the pthread_cond_wait function?

1) The mutex is released

The second parameter in the pthread_cond_wait function is a mutex

2) Wait for the condition

3) The condition is triggered and the mutex is locked

The third step is the atomic operation

Guess you like

Origin blog.csdn.net/qq_58550520/article/details/129089176