一、条件变量:
1、概念:
用线程间共享的全局变量进行同步的机制。条件变量是用来等待事件。条件变量给多个线程提供了一个汇合的场所。
2、原理:
在线程间同步时,自动阻塞一个线程,直到某些条件满足被唤醒,通常与互斥锁一同被使用。
3、函数:
(1)初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
(2)自动释放mutex锁,等待条件满足
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
1)阻塞等待条件变量的满足(参数1)
2)释放已掌握的互斥锁,相当于mutex_unlock
3)当被唤醒的时候,拿锁访问数据(lock),通过wait返回数据,解除阻塞(lock)
(3)销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
(4)唤醒阻塞在条件变量上的至少一个线程
int pthread_cond_signal(pthread_cond_t *cond);
(5)唤醒所有阻塞在条件变量上的线程,通知所有人
int pthread_cond_broadcast(pthread_cond_t *cond);
4、程序设计步骤:
(1)定义一把锁 mutex_t mutex;
(2)对锁初始化 init(&mutex);
(3)拿锁 lock
(4)定义条件变量 cond_t has_product;
(5)初始化条件变量 cond_init();
注:
对于多个消费者线程,先拿锁,再访问公共区域。上锁范围:公共数据区域。
条件变量对应在公共区域,当公共区域没有数据时,阻塞在条件变量上等待。
程序:
//程序1 条件变量-生产者消费者模型
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
struct msg {
struct msg *next;
int num;
};//定义数据信息结构体
struct msg *head;//定义数据信息结构体指针变量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;//定义并初始化条件变量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义并初始化一把锁
//消费者线程执行的函数
void *consumer(void *p)
{
struct msg *mp;
for (;;)
{
pthread_mutex_lock(&lock);//加锁
while (head == NULL)
pthread_cond_wait(&has_product, &lock);//自动释放mutex锁,等待条件满足
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);//解锁
printf("Consume %d\n", mp->num);
free(mp);//释放指针
sleep(rand() % 5);//延时
}
}
//生产者线程执行的函数
void *producer(void *p)
{
struct msg *mp;
//不停的生产
for (;;)
{
//创建节点
mp = malloc(sizeof(struct msg));
//随机生成数据信息
mp->num = rand() % 1000 + 1;
printf("Produce %d\n", mp->num);
pthread_mutex_lock(&lock);//加锁
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);//解锁
//唤醒阻塞在条件变量上的至少一个线程
pthread_cond_signal(&has_product);
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
//定义2个线程
pthread_t pid, cid;
//随机数
srand(time(NULL));
//创建生产者线程
pthread_create(&pid, NULL, producer, NULL);
//创建消费者线程
pthread_create(&cid, NULL, consumer, NULL);
//回收生产者线程
pthread_join(pid, NULL);
//回收消费者线程
pthread_join(cid, NULL);
return 0;
}
程序执行效果:
二、信号量
1、理解
类比:公共区域变为格子数。生产者空格数减少,消费者空格数增加。
2、作用
信号量用于解决线程信号冲突问题,相当于计数器,允许多个线程同时进入临界区。
3、函数
(1)int sem_init(sem_t *sem,int pshared,unsigned value);//初始化信号量对象
参数1:sem--要进行初始化的信号量对象
参数2:pshared--控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量
参数3:value--赋给信号量对象的一个整数类型的初始值
调用成功时,返回 0;
(2)int sem_post(sem_t *sem);//给信号量的值+1
参数1:sem--初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
函数功能实现是一个“原子操作”,即同时对同一个信号量做加“1”操作的两个线程是不会冲突的。信号量的值永远会正确地加上一个“2”,因为有两个线程试图改变它。
(3)int sem_wait(sem_t *sem);//从信号量的值-1,但它永远会先等待该信号量为一个非零值才开始做减法
参数1:sem: 初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
也是一个“原子操作”;
(4)int sem_destroy(sem_t *sem);//对用完的信号量进行清理
参数1:sem: 初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
归还自己占有的一切资源,在清理信号量的时候如果还有线程在等待它,用户就会收到一个错误
程序:
//程序2 信号量-生产者消费者模型
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#define NUM 5//宏定义
int queue[NUM];//整型数组
sem_t blank_number, product_number;//定义生产者信号量和消费者信号量
//生产者线程执行的函数
void *producer(void *arg)
{
int p = 0;
while (1)
{
sem_wait(&blank_number);//生产者信号量的值-1
queue[p] = rand() % 1000 + 1;
printf("Produce %d\n", queue[p]);
sem_post(&product_number);//消费者信号量的值+1
p = (p+1)%NUM;
sleep(rand()%5);
}
}
//消费者线程执行的函数
void *consumer(void *arg)
{
int c = 0;
while (1)
{
sem_wait(&product_number);//消费者信号量的值-1
printf("Consume %d\n", queue[c]);
queue[c] = 0;
sem_post(&blank_number);//生产者信号量的值+1
c = (c+1)%NUM;
sleep(rand()%5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid; //定义2个线程
//初始化生产者信号量对象 值为5
sem_init(&blank_number, 0, NUM);
//初始化消费者信号量对象 值为0
sem_init(&product_number, 0, 0);
pthread_create(&pid, NULL, producer, NULL);//创建生产者线程
pthread_create(&cid, NULL, consumer, NULL);//创建消费者线程
pthread_join(pid, NULL); //回收生产者线程
pthread_join(cid, NULL); //回收消费者线程
//清理消费者信号量对象
sem_destroy(&blank_number);
//清理生产者信号量对象
sem_destroy(&product_number);
return 0;
}
程序执行效果:
三、条件变量封装函数设计
//程序3 条件变量封装函数设计1
#ifndef CPTHREADCOND_H
#define CPTHREADCOND_H
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include "wild.h"
class CPThreadCond
{
public:
CPThreadCond();
~ CPThreadCond();
bool wait();
bool signal();
bool broadcast();
protected:
pthread_cond_t m_cond;//定义条件变量
pthread_mutex_t m_mutex;//定义一把锁
};
#endif // CPTHREADCOND_H
//程序4 条件变量封装函数设计2
#include "cpthreadcond.h"
CPThreadCond::CPThreadCond()//构造函数
{
m_cond=wild::has_product;//条件变量
m_mutex=wild::lock;//一把锁
}
CPThreadCond::~CPThreadCond()//析构函数
{
//销毁条件变量
if (pthread_cond_destroy(&m_cond))
{
perror("m_cond destroy\n");
}
//销毁锁
if(pthread_mutex_destroy(&m_mutex) !=0)
{
perror("mutex destroy\n");
}
}
bool CPThreadCond::wait()
{
//自动释放mutex锁,等待条件满足
int ret = pthread_cond_wait(&m_cond,&m_mutex);
if(ret !=0)
{
perror("mutex lock\n");
}
return ret==0 ?true:false;
}
bool CPThreadCond::signal()
{
//唤醒阻塞在条件变量上的至少一个线程
int ret = pthread_cond_signal(&m_cond);
if(ret !=0)
{
perror("m_cond send\n");
}
return ret==0 ? true:false;
}
bool CPThreadCond::broadcast()
{
//唤醒所有阻塞在条件变量上的线程,通知所有人
int ret = pthread_cond_broadcast(&m_cond);
if (ret != 0)
{
perror("m_cond send\n");
}
return ret == 0 ? true : false;
}
//程序5 条件变量封装函数设计3
#ifndef WILD_H
#define WILD_H
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
class wild
{
public:
wild();
static pthread_cond_t has_product;//定义条件变量
static pthread_mutex_t lock;//定义一把锁
};
#endif // WILD_H
//程序6 条件变量封装函数设计4
#include "wild.h"
//静态变量初始化
pthread_cond_t wild::has_product = PTHREAD_COND_INITIALIZER;//定义条件变量
pthread_mutex_t wild::lock = PTHREAD_MUTEX_INITIALIZER;//定义一把锁
wild::wild()
{
}
//程序7 条件变量封装函数设计5
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include "cpthreadcond.h"
#include "wild.h"
struct msg {
struct msg *next;
int num;
};//定义数据信息结构体
struct msg *head;//定义数据信息结构体指针
CPThreadCond cond;//定义条件变量类对象
//消费者线程执行的函数
void *consumer(void *p)
{
struct msg *mp;
for (;;)
{
pthread_mutex_lock(&(wild::lock));
while (head == NULL)
cond.wait();//自动释放mutex锁,等待条件满足
mp = head;
head = mp->next;
pthread_mutex_unlock(&(wild::lock));
printf("Consume %d\n", mp->num);
delete mp; //释放结点
sleep(rand() % 5);
}
}
//生产者线程执行的函数
void *producer(void *p)
{
struct msg *mp;
//不停的生产
for (;;)
{
//创建节点
mp = new (struct msg);
mp->num = rand() % 1000 + 1;//随机生成数据信息
printf("Produce %d\n", mp->num);
pthread_mutex_lock(&(wild::lock));//加锁
mp->next = head;
head = mp;
pthread_mutex_unlock(&(wild::lock));//解锁
//唤醒阻塞在条件变量上的至少一个线程
cond.signal();
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
//定义2个线程
pthread_t pid, cid;
srand(time(NULL)); //随机数
pthread_create(&pid, NULL, producer, NULL);//创建生产者线程
pthread_create(&cid, NULL, consumer, NULL);//创建消费者线程
pthread_join(pid, NULL);//回收生产者线程
pthread_join(cid, NULL);//回收消费者线程
return 0;
}
程序执行效果:
四、线程同步总结:
线程同步实质为线程排队,只有共享资源的读写访问才需要线程同步。
1、线程同步方式: 互斥锁(也称为互斥量);条件变量;信号量。
2、互斥量:(mutex)
(1)概述
互斥锁用于同步线程对共享数据的访问,即实现多线程程序中的同步访问的另一种手段。
(2)特点
本质是一把锁,访问共享资源时对互斥量设置,即加锁,访问结束,进行解锁。在加锁之后,其他线程试图对该互斥量设置时会被阻塞,直到当前进程释放互斥锁。
3、条件变量:(cond)
(1)概述:利用线程间共享的全局变量进行线程同步,互斥量解锁和条件变量挂起自动进行。条件变量在线程间同步时,自动阻塞一个线程,直到某些条件满足被唤醒,通常与互斥锁一同被使用。
(2)作用和特点:
1)条件变量用于线程之间同步共享数据的值,给多个线程提供一个汇合的场所。
2)条件变量分为条件和变量,条件由互斥量保护,线程在改变条件状态之前必须先锁住条件变量。
3)条件变量可使线程阻塞等待某种条件出现。
(3)与互斥量区别:
条件变量用来等待而非上锁,可以使线程睡眠等待某种条件出现,即自动阻塞一个线程,直到特殊情况发生为止,通常条件变量与互斥锁同时使用。
(4)两个动作:
1)一个线程等待“条件变量条件成立”而挂起;
2)另一线程让条件成立,即发出条件成立信号。
(5)条件检测:
条件检测在互斥锁保护下进行,例如线程1检测到条件为假,线程1自动阻塞,并释放等待状态的互斥锁。
当线程2改变了条件,则会发出信号给关联的条件变量,唤醒一个或多个等待它的线程,比如线程1将重新获得互斥锁,重新检测条件变量。
4、信号量:(sem)
(1)概述
本质上是一个非负的整数计数器,用于对公共资源访问,与进程一样,线程也可通过信号量实现通信。
(2)特点
1)信号量是一个特殊的变量,可被增加和减少,对其关键访问保证是原子操作,即多个线程试图改变信号量值,系统保证所有操作依次进行,不会发生线程冲突。
2)信号量一般用于保护一段代码,每次只能被一个程序执行。等待信号量时给信号量减1,直到信号量值大于0,释放信号量,信号量值加1,并通知其他等待线程。
编程时可根据操作信号值的结果判断是否对公共资源有访问权限,信号量值大于0 ,可访问,否则阻塞,PV原语为对信号量操作,一次P操作使信号量-1,一次V操作使信号量值+1。
(3)分类:
1)二进制信号量--“0”和“1”取值
2)计数信号量--取值范围较大,用于有限个线程执行一段程序
(4)与条件变量的区别
信号量递增和减少会被系统自动记住,系统内部存在一个计数器实现信号量而不必担心信号量值丢失。当唤醒一个条件变量时,如果没有相应的线程在等待该条件变量,此次唤醒将被丢失。