一、互斥锁实例
1> 临界资源(共享资源):在多任务并发执行时,访问的同一个资源
2> 临界区:访问临界资源的代码
3> 竞态:多个任务共同抢占临界资源的现象叫做竞态
4> 互斥:指某一个临界资源同一时刻只允许一个访问者,具有唯一性和排他性
5> 同步:使用相关手段,将任务有先后顺序的执行
6> 解决竞态的方法:互斥锁、无名信号量、条件变量
7> 互斥锁的使用逻辑:
1、给临界区上锁,获取到锁的线程可以使用临界资源
2、释放锁,当一个线程释放锁资源后,其他线程对该锁可以继续抢占
3、互斥锁被一个任务抢占后,其他任务就不能获取到该锁资源了
4、互斥锁的本质是一个全局变量
8> 互斥锁的API
#include <myhead.h>
pthread_mutex_t mutex;//1.定义互斥锁变量
int money = 5000;//临界资源
void *task1(void *arg)
{
while(1)
{
sleep(1);
pthread_mutex_lock(&mutex);//3.上锁
money-=50;
printf("张三取的50元,剩余%d元\n",money);
if(money<=0)
{
printf("没钱了,结束取款\n");
pthread_mutex_unlock(&mutex);//4.解锁
break;
}
pthread_mutex_unlock(&mutex);//4.解锁
}
pthread_exit(NULL);
}
void *task2(void *arg)
{
while(1)
{
sleep(1);
pthread_mutex_lock(&mutex);//3.上锁
money-=100;
printf("李四取的100元,剩余%d元\n",money);
if(money<=0)
{
printf("没钱了,结束取款\n");
pthread_mutex_unlock(&mutex);//4.解锁(防止少一次变死锁)
break;
}
pthread_mutex_unlock(&mutex);//4.解锁
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1,tid2;
pthread_mutex_init(&mutex,NULL);//2.初始化互斥锁
if(pthread_create(&tid1,NULL,task1,NULL))
{
printf("线程1创建失败\n");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
printf("线程2创建失败\n");
return -1;
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&mutex);//销毁锁
return 0;
}
二、无名信号量------生产者消费者模型案例
1> 线程同步:在已经知道线程的执行顺序,并且线程是按顺序进行执行,这种操作称为线程同步
2> 线程同步适用的模型:生产者消费者模型。即生产者先生成商品,然后,消费者在对商品进行消费
3> 无名信号量的api
1、要定义一个无名信号量
sem_t sem; //定义一个信号量
2、初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个无名信号量
参数1:无名信号量地址
参数2:进程或线程同步的标识
0:表示多线程之间的同步
1:表示多个进程之间的同步(亲缘间)
参数3:无名信号量维护的value值,每进行一次wait,那么该值就会减1,每进行一次post,该值就会加1,当该值为0时,wait会阻塞
返回值:成功返回0,失败返回-1并置位错误码
3、申请资源
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:申请当前无名信号量的资源,使得value值减1,当资源为0时,该函数会被阻塞,直到alue值大于0
参数:无名信号量地址
返回值:成功返回0,失败返回-1并置位错误码,而且value值不变
4、释放资源
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:释放资源(V操作,使得value值加1)
参数:无名信号量地址
返回值:成功返回0,失败返回-1并置位错误码,并且value的值不会改变
5、销毁无名信号量
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁无名信号量
参数:无名信号量地址
返回值:成功返回0,失败返回-1并置位错误码
#include <myhead.h>
sem_t sem;//1.定义无名信号量
void *task1(void *arg)//生产者线程
{
int num=5;
while(num--)
{
sleep(2);
printf("%#lx:我生产了一辆路虎\n",pthread_self());
sem_post(&sem);//4.释放资源
}
pthread_exit(NULL);
}
void *task2(void *arg)//消费者线程
{
int num=5;
while(num--)
{
sem_wait(&sem);//3.申请等待
printf("%#lx:我消费了一辆路虎\n",pthread_self());
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1,tid2;
sem_init(&sem,0,0);//2.初始化无名信号量
if(pthread_create(&tid1,NULL,task1,NULL))
{
printf("线程1创建失败\n");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
printf("线程2创建失败\n");
return -1;
}
printf("tid1=%#lx tid2=%#lx\n",tid1,tid2);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
sem_destroy(&sem);//5.销毁无名信号量
return 0;
}
三、条件变量-------生产者消费者模型案例
1> 无名信号量维护了一个value值,而条件变量维护了一个队列
2> 条件变量对应的模型:生产者消费者模型(一个生产者对应多个消费者)
3> 条件变量的API
#include <pthread.h>
1、定义一个条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化
2、初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:动态初始化一个条件变量
参数1:条件变量的地址
参数2:条件变量的属性,默认为NULL
返回值:成功返回0,失败返回非0的错误码
3、给休眠的线程发送唤醒信号
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒休眠队列中的对头线程
参数:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒休眠队列中的所有线程
参数:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
4、阻塞等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:等待条件变量,并让该线程进入休眠队列
参数1:条件变量
参数2:互斥锁,由于进入休眠队列时可能会产生竞态,所以需要一把互斥锁
返回值:成功返回0,失败返回非0的错误码
5、销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数:条件变量地址
返回值:成功返回0,失败返回非0的错误码
#include <myhead.h>
sem_t sem;//1.定义无名信号量
sem_t sem1;
void *task1(void *arg)//生产者线程
{
int num=5;
while(num--)
{
sleep(1);
printf("%#lx:A\n",pthread_self());
sem_post(&sem);//4.释放资源
}
pthread_exit(NULL);
}
void *task2(void *arg)//消费者线程
{
int num=5;
while(num--)
{
sem_wait(&sem);//3.申请等待
printf("%#lx:B\n",pthread_self());
sem_post(&sem1);
}
pthread_exit(NULL);
}
void *task3(void *arg)//消费者线程
{
int num=5;
while(num--)
{
sem_wait(&sem1);//3.申请等待
printf("%#lx:C\n",pthread_self());
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1,tid2,tid3;
sem_init(&sem,0,0);//2.初始化无名信号量
sem_init(&sem1,0,0);//2.初始化无名信号量
if(pthread_create(&tid1,NULL,task1,NULL))
{
printf("线程1创建失败\n");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
printf("线程2创建失败\n");
return -1;
}
if(pthread_create(&tid3,NULL,task3,NULL))
{
printf("线程3创建失败\n");
return -1;
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
sem_destroy(&sem);//5.销毁无名信号量
sem_destroy(&sem1);//5.销毁无名信号量
putchar(10);
return 0;
}
四、无名管道实例
1> 无名管道顾名思义就是没有名字的管道文件,由于该文件没有名字,导致其他进程不能打开该文件,所以就不能完成非亲缘间的进程的通信
2> 无名管道适用于亲缘进程间的通信
3> 无名管道的API
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个管道,用于进程间通信,并通过参数返回该管道的两端,pipefd[0]是管道文件的读端,pipefd[1]是管道文件的写端
参数:整形数组,存放管道文件的文件描述符
返回值:成功返回0,失败返回-1并置位错误码
#include <myhead.h>
int main(int argc, const char *argv[])
{
int pipefd[2];
pid_t pid;
if(pipe(pipefd)==-1)
{
perror("pipe error");
return -1;
}
pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}else if(pid==0)//子进程
{
close(pipefd[0]);
while(1)
{
char buf[128]="";
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
write(pipefd[1],buf,strlen(buf));
if(strcmp(buf,"quit")==0)
{
break;
}
}
close(pipefd[1]);
exit(EXIT_SUCCESS);
}else//父进程
{
close(pipefd[1]);
while(1)
{
char buf[128]="";
read(pipefd[0],buf,sizeof(buf));
if(strcmp(buf,"quit")==0)
{
break;
}
printf("父进程读取了数据:%s\n",buf);
}
close(pipefd[0]);
wait(NULL);
}
return 0;
}
五、有名管道
1> 有名管道顾名思义就是有名字的管道,在创建管道文件时,会给出管道文件的名称
2> 有名管道适用于亲缘或非亲缘进程间的通信
3> 有名管道的api
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数1:有名管道文件的名字
参数2:文件权限,需要&上~umask
返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义进程号
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}else if(pid == 0)
{
//子进程
//创建一个管道文件
mkfifo("./myfifo", 0664);
//1、打开文件
int wfd;
if((wfd=open("./myfifo", O_WRONLY)) == -1)
{
perror("open error");
return -1;
}
while(1)
{
char buf[128] = "";
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0'; //将‘|n’换成'\0'
//将内容写入文件
//2、将内容写入文件
write(wfd, buf, strlen(buf));
//判断写入数据
if(strcmp(buf, "quit") == 0)
{
break;
}
}
//3、关闭文件
close(wfd);
exit(EXIT_SUCCESS);
}else
{
sleep(1);
//父进程
//定义文件描述符
int rfd;
//打开文件
if((rfd=open("./myfifo", O_RDONLY))==-1)
{
perror("open error");
return -1;
}
while(1)
{
char buf[128] = "";
read(rfd, buf, sizeof(buf)); //从管道中读取数据
if(strcmp(buf,"quit") == 0)
{
break;
}
printf("父进程中读取数据:%s\n", buf);
}
//关闭文件
close(rfd);
wait(NULL);
}
return 0;
}