线程间的通信机制
概念
线程之间是很容易进行通信的,能够通过全局变量实现数据的共享和交换,也就是通过访问临界资源,但是多个线程在同时访问共享数据的对象时需要引入同步和互斥机制。
-
什么是临界资源?
一次仅允许一个线程访问的资源叫临界资源。
同步机制的概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
信号量(了解)
内核信号量 由内核控制路径使用
Posix信号量
- a.无名信号量:数据存储在内存中,通常在线程间使用或父子进程间
函数接口:sem_init\sem_wait\sem_post
b.有名信号量:数据存储在文件中,在进程间线程间都可以使用
函数接口:sem_open\sem_wait\sem_post\sem_closeSystem V信号量
是信号量的集合,叫信号灯集,属于IPC对象
函数接口:semget\semctl\semop
无名信号量
信号量:通过信号量实现同步操作,由信号量决定线程是继续运行还是阻塞等待。
信号量代表的是某一类资源,它的值表示系统中该资源的数量,信号量>0的话,表示有资源可以使用,可以申请到资源,继续执行程序,信号量<= 0的话,表示没有资源可以使用,无法申请到资源,阻塞。
信号量是一个受保护的量
函数接口
初始化信号量:sem_init()
P操作,申请资源:sem_wait() 资源-1
V操作,释放资源:sem_post() 资源+1
注意:信号量是一个非负的整数,所以一定是 (>=0)
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:初始化信号量
参数:sem:初始化的信号量对象
pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)
value:信号量初值
返回值:成功 0
失败 -1
int sem_wait(sem_t *sem)
功能:申请资源 P操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int sem_post(sem_t *sem)
功能:释放资源 V操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:释放一次信号量的值加1,函数不阻塞
获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);
功能:获取信号量的值
参数:sem_t *sem:信号量对象
int *sval:信号量的值存放的变量
返回值:成功:0
失败:错误码:
销毁信号量
int sem_destroy(sem_t *sem);
功能:销毁信号量
参数:信号量对象
返回值:成功:0
失败:错误码
#include <stdio.h>
#include <semaphore.h>
int main(int argc, char const *argv[])
{
sem_t sem;
int sval;
//初始化信号量
int semid = sem_init(&sem,0,0);
if (semid < 0)
{
printf("init err");
return -1;
}
//获取信号量的值
sem_getvalue(&sem,&sval);
printf("%d\n",sval);
//申请资源
sem_wait(&sem);
sem_getvalue(&sem,&sval);
printf("%d\n",sval);
sem_wait(&sem);
sem_getvalue(&sem,&sval);
printf("%d\n",sval);
//释放资源
sem_post(&sem);
sem_getvalue(&sem,&sval);
printf("%d\n",sval);
sem_post(&sem);
sem_getvalue(&sem,&sval);
printf("%d\n",sval);
return 0;
}
互斥的概念
互斥:多个线程在访问临界资源时,同一时间只能一个线程进行访问
互斥锁
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。
函数接口
1. 初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
功能:初始化互斥锁
参数:mutex:互斥锁
attr: 互斥锁属性 // NULL表示缺省属性
返回值:成功 0
失败 -1
2. int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:申请互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
3. int pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:释放互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
4. int pthread_mutex_destroy(pthread_mutex_t *mutex)
功能:销毁互斥锁
参数:mutex:互斥锁
两个线程实现一个循环倒置,一个循环打印。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
pthread_mutex_t mutex;
// 倒置线程
void *myrever(void *arg)
{
int temp;
while (1)
{
// 上锁
pthread_mutex_lock(&mutex);
for (int i = 0; i < 5; i++)
{
temp = a[i];
a[i] = a[9 - i];
a[9 - i] = temp;
}
// 解锁
pthread_mutex_unlock(&mutex);
}
}
// 打印线程
void *myprint(void *arg)
{
while (1)
{
// 上锁
pthread_mutex_lock(&mutex);
for (int i = 0; i < 10; i++)
{
printf("%d", a[i]);
}
printf("\n");
// 解锁
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main(int argc, char const *argv[])
{
// 初始化锁;
if (pthread_mutex_init(&mutex, NULL))
{
printf("init err\n");
return -1;
}
// 创建两个线程
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, myrever, NULL))
{
printf("creat err\n");
return -1;
}
if (pthread_create(&tid2, NULL, myprint, NULL))
{
printf("creat err\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
条件变量
一般与互斥锁搭配使用,实现同步机制
使用步骤
- pthread_cond_init:初始化
- pthread_cond_wait:阻塞等待条件产生,没有条件产生时阻塞,同时解锁,当条件产生时结束阻塞,再次上锁
- pthread_cond_signal:产生条件,不阻塞
- pthread_cond_wait先执行,pthread_cond_signal再产生条件
函数接口
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数:cond:是一个指向结构pthread_cond_t的指针
restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
返回值:成功:0 失败:非0
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:等待信号的产生
参数:restrict cond:要等待的条件
restrict mutex:对应的锁
返回值:成功:0,失败:不为0
注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。
int pthread_cond_signal(pthread_cond_t *cond);
功能:给条件变量发送信号
参数:cond:条件变量值
返回值:成功:0,失败:非0
注:必须等待pthread_cond_wait函数先执行,再产生条件才可以
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0
示例代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
pthread_mutex_t mutex;
pthread_cond_t cond;
// 倒置线程
void *myrever(void *arg)
{
int temp;
while (1)
{
sleep(1);
// 上锁
pthread_mutex_lock(&mutex);
for (int i = 0; i < 5; i++)
{
temp = a[i];
a[i] = a[9 - i];
a[9 - i] = temp;
}
//产生条件
pthread_cond_signal(&cond);
// 解锁
pthread_mutex_unlock(&mutex);
}
}
// 打印线程
void *myprint(void *arg)
{
while (1)
{
// 上锁
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
for (int i = 0; i < 10; i++)
{
printf("%d", a[i]);
}
printf("\n");
// 解锁
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char const *argv[])
{
// 初始化锁;
if (pthread_mutex_init(&mutex, NULL))
{
printf("init err\n");
return -1;
}
// 初始化条件变量
if (pthread_cond_init(&cond, NULL))
{
printf("init err\n");
return -1;
}
// 创建两个线程
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, myrever, NULL))
{
printf("creat err\n");
return -1;
}
if (pthread_create(&tid2, NULL, myprint, NULL))
{
printf("creat err\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
补充
避免僵尸进程的方式
1.wait:阻塞,一定保证能回收
2.waitpid:阻塞/非阻塞,非阻塞的方式不一定能回收到进程,需要使用轮询(循环操作)
while(1)
{
if(waitpid(-1, NULL, WNOHANG) > 0)
break;
}
IO模型(了解)
阻塞IO:阻塞,实时性比较差,需要阻塞等待,但是不占用CPU
非阻塞IO:需要轮询操作(循环操作),会占用CPU,消耗系统性
互斥锁 死锁
是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
死锁产生的四个必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等 待环路。
注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。