同步的几种方式:信号量,互斥锁,条件变量,读写锁
同步:对程序的执行过程进行控制,保证对临界资源的访问同一时刻只能有一个进程或线程访问。
2.1信号量
存在P操作:获取资源,信号量值-1;和V操作:释放资源,信号量值+1。
临界资源:同一时刻只允许一个线程或进程访问的资源。
临界区:访问临界资源的代码段。
二值信号量:信号量的值只有0和1
计数信号量:信号量值最大值大于1
2.2线程信号量接口函数与应用
sem_init(sem_t* sem , int pshared , unsigned int value) 初始化信号量 第一个参数为信号量地址;第二个参数为是否在两个进程间共享,不共享就传0;第三个为初始值
sem_wait(sem_t* sem) P操作
sem_post(sem_t* sem) V操作
sem_destroy(sem_t* sem) 销毁信号量
场景实现:三个线程分别能打印A B C,使用信号量实现顺序打印ABCABCABC...
实现原理:
实现代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<semaphore.h>
#include<pthread.h>
sem_t sema,semb,semc;
void* funa(void* arg)
{
for(int i=0;i<5;++i)
{
sem_wait(&sema);
printf("A");
fflush(stdout);
sem_post(&semb);
}
}
void* funb(void* arg)
{
for(int i=0;i<5;++i)
{
sem_wait(&semb);
printf("B");
fflush(stdout);
sem_post(&semc);
}
}
void* func(void* arg)
{
for(int i=0;i<5;++i)
{
sem_wait(&semc);
printf("C");
fflush(stdout);
sem_post(&sema);
}
}
int main()
{
sem_init(&sema,0,1);
sem_init(&semb,0,0);
sem_init(&semc,0,0);
pthread_t id1,id2,id3;
pthread_create(&id1,NULL,funa,NULL);
pthread_create(&id2,NULL,funb,NULL);
pthread_create(&id3,NULL,func,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_join(id3,NULL);
sem_destroy(&sema);
sem_destroy(&semb);
sem_destroy(&semc);
exit(0);
}
运行结果:
2.3互斥锁(互斥量)
操作: 加锁 解锁
相关函数:
pthread_mutex_init() 初始化
pthread_mutex_lock() 加锁 会存在阻塞
pthread_mutex_unlock() 解锁
pthread_mutex_destroy() 销毁
场景使用:(以1.6的代码为例,使用互斥锁实现不会出现小于5000)
#define MAXID 5
int val=1;//全局变量
pthread_mutex_t mutex;
void* fun(void* arg)
{
for(int i=0;i < 1000;++i)
{
pthread_mutex_lock(&mutex);
printf("%d\n",val++);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id[MAXID];
for(int i = 0;i < MAXID; ++i)
{
pthread_create(&id[i],NULL,fun,NULL);
}
for(i = 0;i < MAXID; ++i)
{
pthread_join(id[i],NULL);
}
pthread_mutex_destroy(&mutex);
exit(0);
}
运行结果总是到5000,不会出现小于5000的情况
上述操作可以使用信号量代替,能使用互斥锁的情况一定也可以使用信号量。该种情况使用互斥锁方便,而信号量需要设置初值等。
2.4读写锁
接口函数:
pthread_rwlock_init() 初始化
pthread_rwlock_rdlock() 读锁
pthread_rwlock_wrlock() 写锁
pthread_rwlock_unlock() 解锁
pthread_rwlock_destroy() 销毁
与互斥锁的区别:对于多个线程只读数据时,读数据的线程因为都不会改变数据,就可以同时执行。使用互斥锁同一时刻只能有一个线程读取,会造成程序性能降低。而读写锁分为读锁和写锁,如果对于多个不同线程都加的是读锁的话,多个线程会同时去读数据。只有多个读可以一起执行,一读一写或者多个写都不可以,会进行阻塞。
适用场景:读取数据的线程多,写的线程少。
模拟使用读写锁:(两个读一个写)
pthread_rwlock_t lock;
void* fun_r1(void* arg)
{
for(int i=0;i<10;++i)
{
pthread_rwlock_rdlock(&lock);
printf("fun1 read start\n");
sleep(1);
printf("fun1 read end\n");
pthread_rwlock_unlock(&lock);
}
}
void* fun_r2(void* arg)
{
for(int i=0;i<5;++i)
{
pthread_rwlock_rdlock(&lock);
printf("fun2 read start\n");
sleep(2);
printf("fun2 read end\n");
pthread_rwlock_unlock(&lock);
}
}
void* fun_w(void* arg)
{
for(int i=0;i<3;++i)
{
pthread_rwlock_wrlock(&lock);
printf("------write start------\n");
sleep(3);
printf("------write end------\n");
pthread_rwlock_unlock(&lock);
}
}
int main()
{
pthread_rwlock_init(&lock,NULL);
pthread_t id1,id2,id3;
pthread_create(&id1,NULL,fun_r1,NULL);
pthread_create(&id2,NULL,fun_r2,NULL);
pthread_create(&id3,NULL,fun_w,NULL);
pthread_join(&id1,NULL);
pthread_join(&id2,NULL);
pthread_join(&id3,NULL);
pthread_rwlock_destroy(&lock);
exit(0);
}
只要写操作一启动,则两个读操作都无法抢占,而在fun2去执行的同时fun1也可以去执行。即多个读操作可以同时执行,而写操作同时只能一个执行。
2.5条件变量
接口函数:
pthread_cond_init() 初始化
pthread_cond_broadcast() 唤醒在条件变量上等待的所有线程
pthread_cond_signal() 唤醒在条件变量上等待的一个线程
pthread_cond_wait() 添加线程进入条件变量的等待队列
第一个参数为条件变量地址;第二个参数为互斥锁地址,目的是为了解锁和加锁,所以才使用次函数前需要加锁,后需要解锁。因为进入等待队列时为了确保同一时间只能有一个线程入队才需要手动加锁,进入完成后wait函数内部会实现解锁;然后阻塞住;直到被唤醒后,该线程就会出队列,为了确保同一时间只能有一个线程出队,wait函数内部会实现加锁,然后出队后手动解锁。
上述的手动是需要自己加入加锁和解锁函数
pthread_cond_destroy() 销毁
实现三个线程模拟使用条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
void funa(void* arg)
{
char* s=(char*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);//该线程加入等待队列后,会在wait函数内部释放锁,使得其他线程可以继续使用该互斥锁。
pthread_mutex_unlock(&mutex);
//因为进入等待队列时为了确保同一时间只能有一个线程入队才需要手动加锁,进入完成后wait函数内部会实现解锁;然后阻塞住,等到该线程出队列时,为了确保同一时间只能有一个线程出队,wait函数内部会实现加锁,然后出队后手动解锁
if(strncmp(s,"end",3)==0)
{
break;
}
printf("A thread read :%s",s);
}
printf("funa over\n");
}
void funb(void* arg)
{
char* s=(char*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
if(strncmp(s,"end",3)==0)
{
break;
}
printf("B thread read :%s",s);
}
printf("funb over\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
char buff[128]={0};
pthread_t ida,idb;
pthread_create(&ida,NULL,funa,buff);
pthread_create(&idb,NULL,funb,buff);
while(1)
{
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
pthread_cond_broadcast(&cond);
break;
}
else
{
pthread_cond_signal(&cond);
}
}
pthread_join(ida,NULL);
pthread_join(idb,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
运行结果