注:本文用于个人学习/复习,代码参考 互斥锁_哔哩哔哩_bilibili
1、线程同步
多个线程访问同一块共享资源,需要按照顺序依次执行访问,线程对内存的这种访问方式被称之为线程同步
实现线程同步有以下的几种方法:
互斥锁
条件变量
信号量
自旋锁
读写锁
2、互斥锁
1)初始化锁
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)阻塞加锁,若互斥锁已经被其他线程上锁,则调用者一直阻塞等待,直到被解锁后才上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
3)非阻塞加锁,若互斥锁未加锁,则上锁,若互斥锁已加锁,则函数立即返回失败
int pthread_mutex_trylock(pthread_mutex_t *mutex);
4)解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5)销毁锁,释放资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁存在优先唤醒的过程
测试demo如下:
#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;
}
执行结果如下:
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
从测试log可以看出,各个线程逐个逐个访问全局变量buffer
3、条件变量
条件变量用来阻塞一个线程,直到其他的线程通知它条件已满足为止
条件变量看似简单,与互斥锁同时使用时非常巧妙
1)初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
或者定义条件变量的时候直接使用下面语句对条件变量进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2)阻塞等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
3)超时等待
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const timespec *abstime);
4)唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);
5)唤醒所有等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
6)销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
测试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;
}
测试结果:
另外一个终端的输入:
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_cond_signal函数注释,打开pthread_cond_broadcast函数的注释,就可以得到以下的结果:
另外一个终端的输入:
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、条件变量与互斥锁的关系
测试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;
}
执行结果如下:
另一个终端:
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
pthread_cond_wait函数里面发生了什么?
1)释放了互斥锁
pthread_cond_wait函数里面第二个参数是互斥锁
2)等待条件
3)条件被触发并且给互斥锁加锁
第三步是原子操作