多任务互斥和同步
互斥和同步概述
在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源,多个任务之间有依赖关系,某个任务的运行依赖于另一个任务,同步和互斥就是用于解决这两个问题的
互斥
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源,POSIX标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式
同步
两个或两个以上的进程或线程在运行过程中协调步调,按预定的先后次序运行同步就是在互斥的基础上有顺序
互斥锁
互斥锁的概念:
mutex是一种简单的加锁的方法来控制对共享资源的访问,mutex只有两种状态,即上锁(lock)和解锁(unlock)在访问该资源前,首先应申请mutex,如果mutex处于unlock状态,则会申请mutex并立即lock;
如果mutex处于lock状态,则默认阻塞申请者
unlock操作应该由lock者进行
互斥锁的操作
创建互斥锁
mutex用pthread_mutex_t数据类型表示,在使用互斥锁前,必须先对它进行初始化
静态分配的互斥锁
pthread_mutex_t mutex= PTHREAD_MUTEX_INITALIZER;
动态分配互斥锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);
在所有使用过此互斥锁的线程都不再需要使用时候,应调用
pthread_mutex_destory销毁互斥锁
初始化互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t * attr)
功能:
初始化一个互斥锁
参数:
mutex: 互斥锁地址
attr: 互斥锁的属性,NULL为默认的属性
返回值
成功返回0,失败返回非0
互斥锁上锁(1)最常用的
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能
对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁
参数
mutex: 互斥锁地址,也就是指定的互斥锁
返回值
成功返回0,失败返回非0
互斥锁上锁(2)不太常用的
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能
对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回
参数
mutex: 互斥锁地址
返回值
成功返回0,失败返回非0
互斥锁解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t * mutex);
功能
对指定的互斥锁解锁
参数
mutex: 互斥锁地址
返回值
成功返回0,失败返回非0
销毁互斥锁
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能
销毁指定的一个互斥锁
参数
mutex: 互斥锁地址
返回值
成功返回0,失败返回非0
互斥锁的使用实例
不使用互斥锁的后果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int money = 10000;
void *pthread_fun1(void* arg)
{
int get, yu, shiji;
get = 10000;
printf("张三正在查询余额... \n");
sleep(1);
yu = money;
printf("张三正在取钱... \n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("张三想去 %d钱 实际去了 %d钱 ,余额为 %d钱\n", get ,shiji, yu);
pthread_exit(NULL);
}
void * pthread_fun2(void* arg)
{
int get, yu, shiji;
get = 10000;
printf("李四正在查询余额\n");
sleep(1);
yu = money;
printf("李四正在取钱\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf(" 李四想取 %d钱 实际取了 %d 钱 余额为%d钱\n", get, shiji, yu);
pthread_exit(NULL);
}
int main()
{
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
return 0;
}
一共10000元,主卡,和副卡,两个人一起取,结果两个人一人取了1万,这样合理吗,肯定不合理,银行不会让你这样干
使用互斥锁之后
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int money = 10000;
---------第一步---------互斥锁使用第一步创建互斥锁---
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
void *pthread_fun1(void* arg)
{
int get, yu, shiji;
get = 10000;
---------第三步---------对共享资源的操作进行上锁---
pthread_mutex_lock(&mymutex);
printf("张三正在查询余额... \n");
sleep(1);
yu = money;
printf("张三正在取钱... \n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("张三想去 %d钱 实际去了 %d钱 ,余额为 %d钱\n", get ,shiji, yu);
---------第五步---------对共享资源操作执行完之后,对互斥锁进行解锁操作---
pthread_mutex_unlock(&mymutex);
pthread_exit(NULL);
}
void * pthread_fun2(void* arg)
{
int get, yu, shiji;
get = 10000;
---------第四步---------对共享资源的操作进行上锁---
pthread_mutex_lock(&mymutex);
printf("李四正在查询余额\n");
sleep(1);
yu = money;
printf("李四正在取钱\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf(" 李四想取 %d钱 实际取了 %d 钱 余额为%d钱\n", get, shiji, yu);
---------第六步---------对共享资源操作执行完之后,对互斥锁进行解锁操作---
pthread_mutex_unlock(&mymutex);
pthread_exit(NULL);
}
int main()
{
---------第二步---------初始化互斥锁-------
pthread_mutex_init(&mymutex,NULL);
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
---------第七步--------对互斥锁使用完之后要销毁互斥锁-------
pthread_mutex_destroy(&mymutex);
return 0;
}
信号量
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞
P V原语是对信号量的操作一次P操作使信号量sem减1,一次V操作使信号量sem加1,对于P操作,如果信号量的sem值为小于等于0,则P操作就会阻塞,如果信号量的值大于0,才可以执行p操作进行减1,
信号量主要用于进程或线程间的同步和互斥这两种典型情况
1.若用于互斥,几个进程(或线程)往往只设置一个信号量
2.若用于同步操作,往往会设置多个信号量,并且安排不同的初始值,来实现它们之间的执行顺序
信号量用于互斥
信号量用于互斥,首先要初始化信号量,执行p操作,线程1执行,然后执行v操作,第二还是p操作线程2执行,在执行v操作也就意味着,信号量的值由于为1了,所以说这两个线程谁先执行p操作,一旦某一个线程先执行了p操作,那么此时信号量就会变成了0,所以另外一个线程一看信号量的值为0没有办法去执行p操作就会阻塞,所以当一个线程执行完之后呢,执行完v操作,就把原本的信号量的值+1,那么从0变为1之后,那么另外一个线程,就可以正常去执行了,这就是信号量用于互斥它的基本性质
信号量用于同步
这里设置了两个信号量,(如果只有两个线程的话,实际设置一个也行),先把信号量1的值设置为1,把信号量二的值设置为0,线程1执行sem1的p操作,线程2执行sem2的p操作,由于sem2初始值为0,所以线程2的位置就会阻塞,所以先执行线程1的p操作,执行完之后,正常运行线程1,当线程一操作完之后,执行线程2的v操作把sem2的值+1变成1,变成1之后那么我的线程2就可以正常执行p操作了,这就是通过信号量来实现一个同步的问题
>信号量的操作
信号量的初始化
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能
创建一个信号量并初始化它的值
参数
sem: 信号量的地址
pshared: 等于0,信号量在线程间共享;不等于0,信号量在进程间共享
value: 信号量的初始值
返回值
成功返回0,失败返回-1
信号量的p操作
#include<semaphore.h>
int sem_wait(sem_t* sem);
功能
将信号量的值减1,若信号量的值小于等于0,此函数会引起调用者阻塞
参数
sem; 信号量地址
返回值
成功返回0,失败返回-1
下面这个不常用,非阻塞不常用
#include<semaphore.h>
int sem_trywait(sem_t *sem);
功能
将信号量的值减1,若信号量的值小于等于0,则对信号量的操作失败,函数立即返回
参数
sem: 信号量地址
返回值
成功返回0,失败返回-1
信号量的v操作
#include<semaphore.h>
int sem_post(sem_t *sem);
功能
将信号量的值加1并发出信号唤醒等待线程
参数
sem: 信号量地址
返回值
成功返回0,失败返回-1
获取信号量的计数值
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
功能
获取sem标识的信号量的值,保存在sval中
参数
sem: 信号量地址
sval: 保存信号量值 的地址
返回值
成功返回0,失败返回-1
信号量的销毁
#include <semaphore.h>
int sem_destory(sem_t *sem);
功能
删除sem表示的信号量
参数
sem: 信号量地址
返回值
成功返回0,失败返回-1
信号量的使用
信号量实现互斥功能
首先没有加入信号量
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void printer(char* str)
{
while(*str)
{
putchar(*str); //输出一个字符
fflush(stdout);//刷新一下缓冲区
str++;
sleep(1); //每过一秒输出一个字符
}
}
void* thread_fun1(void* arg)
{
char* str1 = "hello";
printer(str1);
}
void* thread_fun2(void* arg)
{
char* str2 = "world";
printer(str2);
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("\n");
return 0;
}
可以看出结果不是我们想要的hello world 或者 world hello 都可以
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//第一步 创建一个信号量
sem_t sem;
void printer(char* str)
{
//第三步执行p操作
//由于使用信号量实现互斥,信号量的初始值设置为1,则两个线程执行p操作
//先执行p操作的线程继续执行,后执行p操作的先阻塞等待
sem_wait(&sem);
while(*str)
{
putchar(*str); //输出一个字符
fflush(stdout);//刷新一下缓冲区
str++;
sleep(1); //每过一秒输出一个字符
}
//第四步执行v操作
sem_post(&sem);
}
void* thread_fun1(void* arg)
{
char* str1 = "hello";
printer(str1);
}
void* thread_fun2(void* arg)
{
char* str2 = "world";
printer(str2);
}
int main()
{
//第二步初始化信号量
sem_init(&sem, 0, 1);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("\n");
sem_destroy(&sem);
return 0;
}
相当于同时打印2个文件,不能第一行第一个文件内容,第二行第二个文件内容
信号量实现同步功能。
同步就是在互斥的基础上有顺序的进行打印
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
char ch = 'a';
void* pthread_g(void* arg)
{
while(1)
{
ch++;
sleep(1);
}
}
void* pthread_p(void* arg)
{
while(1)
{
printf("%c", ch);
fflush(stdout);
}
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, pthread_g, NULL);
pthread_create(&tid2, NULL, pthread_p, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("\n");
return 0;
}
我们想要的是每过一秒打印a 然后这样子,但结果显然不一样
所以要加上信号量解决同步问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
char ch = 'a';
//使用信号量实现同步功能,如果两个线程实现同步,需要通过两个信号量
//第一步创建两个信号量
sem_t sem_g, sem_p;
void* pthread_g(void* arg)
{
while(1)
{
//第四步后执行的线程中,将信号量的初始值设置为0的信号量执行p操作
sem_wait(&sem_g);
ch++;
sleep(1);
sem_post(&sem_p);
//第六步:后执行的线程执行完毕后,信号量初始值为1的信号量执行v操作
}
}
void* pthread_p(void* arg)
{
while(1)
{
//先执行的线程中,将信号量初始值设置为1的信号量执行p操作
sem_wait(&sem_p);
printf("%c", ch);
fflush(stdout);
sem_post(&sem_g);
//第五步:当先执行的线程执行完毕后,需要将信号量初始值为0的信号量执行v操作
}
}
int main()
{
// 第二步初始化信号量
sem_init(&sem_g, 0 ,0);
sem_init(&sem_p, 0 ,1);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, pthread_g, NULL);
pthread_create(&tid2, NULL, pthread_p, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("\n");
//第七步使用完销毁信号量
sem_destroy(&sem_g);
sem_destroy(&sem_p);
return 0;
}
结果如下:
我们先让它打印,然后再变,然后再打印,就用信号量实现了线程之间的同步问题