目录
线程同步
死锁的概念
死锁是什么?
死锁的四个必要条件
避免死锁
同步的概念与竞态条件
理解同步
概念
条件变量
条件变量的初始化
对条件变量的操作
使用同步控制多线程执行任务
前面我们知道了,如果访问一个全局数据,或者静态的数据,我们往往是需要加锁的,那么如果这种资源没了呢?
死锁往往就是各个执行流占有一定资源且不会释放,并且还再申请其他执行流锁持有的资源,此时就会处于一种永久等待的状态。
这种情况下,所以进程的执行不会被推进,而这就叫死锁。
那么如果想要形成死锁是需要几个条件的:
互斥条件:一个资源每次只能被一个执行流执行
请求与保持:一个执行流请求资源时被阻塞,对已获得的资源保持不放
不剥夺:一个执行流在已获得资源,在未使用完之前,对资源不放
循环等待:若干执行流之间形成一i中头尾相接的循环等待资源关系
什么是必要条件呢?
也就是说,只要形成了死锁,那么就一定会有这四个条件的,所以只需要我们破坏掉一个或者多个条件,那么就可以避免死锁。
那么如何避免死锁呢?
第一个互斥条件:互斥条件是为了保证临界资源访问的正确性,所以如果普坏掉互斥条件的话,同时代码也就是有问题的,所以互斥条件并不能被破坏。
第二个请求与保持:这个条件和不剥夺其实是优点类似的,所以其实可以在让如果资源没有满足的话,那么就释放掉自己的资源,如果这样的话,那么就可以破坏掉死锁,而也可以让线程剥夺资源,这样也可以避免死锁。
第三个循环等待:假设一个线程在等待已经有了A资源,临一个线程竞争到了B资源,那么第一个线程此时需要B资源,但是第二个线程此时需要A资源,那么此时就形成了循环等待,所以为了避免死锁,可以让资源的请求顺序一致。
在说同步之前,我们先理解一下什么是同步!
现在你是一个学生,你们现在下课了,然后都跑去食堂吃饭。
此时由于打饭的窗口比较少,所以只能很多人排队到一个窗口,由于打饭的阿姨只有一个,所以每一次只能给一个人打饭。
此时你在排队的时候,你前面有一个又高又壮的男生,此时到他打饭了,然后他打完饭之后,瞬间就吃完了,然后他还是在打饭,就这样,他刚打完饭就吃掉,然后食堂阿姨又给打,就这样一直下去。
因为他排队了,并没有随便插队,而且打饭的阿姨也只有,阿姨只关心打饭,所以阿姨不管是谁,只要排队到这里了,那么他就给打饭,所以此时你和你后面的同学都吃不到饭。
那么这个又高又壮的男生错了吗?没有错,因为人家也是排队了。
但是合理吗?不合理!
因为这样做的话,那么就会让你和你后面的同学有饥饿问题。
那么应该怎么样呢?应该让打完饭的同学,如果还想继续打饭,那么就应该重新拍到队尾去,此时让你们都是按照某种特定的顺序去打饭。
同步:同步就是在保证数据安全的前提下,让线程按照某种特定的顺序访问临界资源,从而有效的避免饥饿问题,就叫做同步。
竞态条件:因为系统调用执行流的时序问题,从而导致程序异常,这就叫做竞态条件。
上面的概念知识一个简单的理解,下面先看代码来理解一下:
条件变量就是为了同步,所以下面看一下条件变量,其实条件变量和mutex基本一样,接口也是很相同的。
那么我们在说一下为什么需要条件变量,我们前面说了一个关于同步的理解,那么下面说一下关于条件变量的理解同时对同步继续理解一下。
假设你现在去买手机,这部手机是卖的很好,现在你先想要去店里买手机了。
此时你到了店里,你问店员说,xxx手机多少钱,你想要买,但是店员告诉你说很不好意思,这部手机现在卖完了,目前还没有到货,所以你只能回去了。
那么到了第二天,此时你又去了那家店里,你问店员说那不手机到了没有,此时店员还是回答没有,然后由于你害怕手机到货后自己买不到,所以你每天都去店里问一下,你每天都去问,店员看着你都觉得烦的很。
那么此时你做错了吗?没有做错!
那么合理吗?不合理!
为什么?因为你这样做,不仅在浪费自己的时间,同时也是在浪费店员的时间,也就是你在浪费资源。
那么应该怎么做呢,你应该让店员等手机到货后通知你一声,这样你就不用每天都跑过去浪费时间了。
而此时店员告诉你这个过程也叫做同步。
而手机到没到,此时这就是你在手机这个条件下等待,如果手机没到的话,那么你就不用忙活了,你在家等就好了,如果手机到了,那么店员告诉你。
上面的这个就是条件变量和同步。
NAME
pthread_cond_destroy, pthread_cond_init - destroy and initialize condition variables
SYNOPSIS
#include
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_init:是关于条件变量的初始化的,和 pthread_mutex_init 基本一样,只需要传入 pthread_cond_t 的变量即可。
如果 pthread_cond_t 的变量是全局的,那么也可以使用 PTHREAD_COND_INITIALIZER 的宏来初始化。
pthread_cond_init 的第二个参数也是和mutex 的初始化函数相同,也是设置条件变量的属性的,一般我们设置为 nullptr 即可。
如果是使用的局部变量,那么就需要使用 pthread_cond_init 来初始化,但是使用这个函数初始化后,那么就需要使用 pthread_cond_destroy 函数来释放。
还有两个函数是用来对条件变量操作的,和 mutex 基本相同,mutex 的操作有个 lock 和 unlock 操作,那么条件变量是用来控制同步的,那么当然需要控制线程的启动与终止,所以就有两个 wait 和 signal ,意思就是在对应的条件变量下面等待,还有就是唤醒等待的线程。
NAME
pthread_cond_timedwait, pthread_cond_wait - wait on a condition
SYNOPSIS
#include
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
这个函数就是在某个条件变量下等待,如果这个条件不成立,那么就阻塞。
第一个参数就是在哪一个条件变量下等待,传入一个 pthread_cond_t 的指针。
第二个参数就是一个锁的指针。
NAME
pthread_cond_broadcast, pthread_cond_signal - broadcast or signal a condition
SYNOPSIS
#include
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_signal 该函数是唤醒某个条件变量下的一个线程。
pthread_cond_broadcast 该函数是唤醒所有的在某个条件变量下等待的线程。
其中这两个函数的参数都是某个条件变量。
下面我们使用一份代码来看一下同步如何使用:
先说一下我们下面想写的代码逻辑:
我们想创建一批线程,然后让这些线程执行自己的任务。
为了控制线程的同步,我们使用 mutex 和 cond 来控制。
所以我们为了能让这些线程在同一把锁和同一个条件变量的条件下,我们需要将锁与条件变量传给线程。
所以我们需要一个结构体/类来将这些属性抽象出来,然后定义对象传给线程。
我们需要记录这些线程的id,方便后面join
为了方便控制线程,我们还可以使用一个全局的数据来控制:
// 宏的定义和全局的数据
#define PTHREAD_NUM 5
volatile int quit = 0;
线程要执行的任务
void *threadRun1(void *args)
{
threadDate *date = reinterpret_cast(args);
while (true)
{
pthread_mutex_lock(date->_mutex);
// cout << "isquit: " << !quit << endl;
if (!quit)// 访问临界资源也是计算,需要加锁
{
// 等待需要在 条件变量 锁
pthread_cond_wait(date->_cond, date->_mutex);
if(!quit) cout << date->_name << "[ "<< pthread_self() << "] "<< "执行下载任务 Download...." << endl;
pthread_mutex_unlock(date->_mutex);
}
else
{
cout << "线程 " << pthread_self() << "退出" << endl;
pthread_mutex_unlock(date->_mutex);
break;
}
}
delete date;
}
void *threadRun2(void *args)
{
threadDate *date = reinterpret_cast(args);
while (true)
{
pthread_mutex_lock(date->_mutex);
// cout << "isquit: " << !quit << endl;
if (!quit)
{
pthread_cond_wait(date->_cond, date->_mutex);
if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行播放任务 Play...." << endl;
pthread_mutex_unlock(date->_mutex);
}
else
{
cout << "线程 " << pthread_self() << "退出" << endl;
pthread_mutex_unlock(date->_mutex);
break;
}
}
delete date;
}
void *threadRun3(void *args)
{
threadDate *date = reinterpret_cast(args);
while (true)
{
pthread_mutex_lock(date->_mutex);
// cout << "isquit: " << !quit << endl;
if (!quit)
{
pthread_cond_wait(date->_cond, date->_mutex);
if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行上传任务 Uploading...." << endl;
pthread_mutex_unlock(date->_mutex);
}
else
{
cout << "线程 " << pthread_self() << "退出" << endl;
pthread_mutex_unlock(date->_mutex);
break;
}
}
delete date;
}
void *threadRun4(void *args)
{
threadDate *date = reinterpret_cast(args);
while (true)
{
pthread_mutex_lock(date->_mutex);
// cout << "isquit: " << !quit << endl;
if (!quit)
{
pthread_cond_wait(date->_cond, date->_mutex);
if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行存储任务 Storage...." << endl;
pthread_mutex_unlock(date->_mutex);
}
else
{
cout << "线程 " << pthread_self() << "退出" << endl;
pthread_mutex_unlock(date->_mutex);
break;
}
}
delete date;
}
void *threadRun5(void *args)
{
threadDate *date = reinterpret_cast(args);
while (true)
{
pthread_mutex_lock(date->_mutex);
// cout << "isquit: " << !quit << endl;
if (!quit)
{
pthread_cond_wait(date->_cond, date->_mutex);
if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行发生任务 Send...." << endl;
pthread_mutex_unlock(date->_mutex);
}
else
{
cout << "线程 " << pthread_self() << "退出" << endl;
pthread_mutex_unlock(date->_mutex);
break;
}
}
delete date;
}
传给线程的结构体
// 线程的数据
struct threadDate
{
public:
threadDate(pthread_mutex_t *mutex, pthread_cond_t *cond, string name)
: _mutex(mutex), _cond(cond), _name(name)
{
}
public:
pthread_mutex_t *_mutex;// 为了让临界资源的访问安全,需要的锁
pthread_cond_t *_cond; // 控制线程同步的条件变量
string _name; // 线程名
};
主线程执行的函数
void test1()
{
// 定义锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
// 初始化锁和条件变量
pthread_mutex_init(&mutex, nullptr);
pthread_cond_init(&cond, nullptr);
// 函数指针 类型为返回值 void* 参数为 void*
typedef void *(*func)(void *);
// 为每个线程做不同的任务
vector funcs{threadRun1, threadRun2, threadRun3, threadRun4, threadRun5};
// 用来存储线程 ID,方便后面 join
vector pthreadID;
pthread_t tid;
// 创建一批线程
for (int i = 0; i < PTHREAD_NUM; ++i)
{
string name = {"thread "};
name += to_string(i + 1);
// 将线程的数据构建一个对象,然后传给线程
threadDate *date = new threadDate(&mutex, &cond, name);// 这个对象传给了线程,所以线程使用完之后是需要释放的
pthread_create(&tid, nullptr, funcs[i], date);
pthreadID.push_back(tid);
// cout << "创建线程 " << tid << " 成功" << endl;
}
sleep(1);
// 主线程一次唤醒一个线程
for(int i = 1; ; ++i)
{
cout << "main thread[ " << pthread_self() << " ]第 " << i << " 次唤醒线程" << endl;
// pthread_cond_signal(&cond);
pthread_cond_broadcast(&cond);
sleep(1);
if(i == 5)
{
quit = 1;
pthread_cond_broadcast(&cond);
break;
}
}
// 等待线程
for (int i = 0; i < PTHREAD_NUM; ++i)
{
pthread_join(pthreadID[i], nullptr);
cout << "等待线程 " << pthreadID[i] << "成功" << endl;
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}