线程同步
由于在一个进程中,各个线程都可以操作共享数据,cpu的调度问题,可能会导致数据混乱。例如两个线程在操作同一个全局变量,一个线程在对变量进行修改后,还为来得及将修改的变量写入内存,cpu就结束对这个线程的运行,去运行另一个线程。如果另一个线程也对这个变量进行了修改,并写入了内存,就可能导致数据混乱。这就需要线程同步来解决这个问题。
一、互斥锁
1.互斥锁类型
pthread_mutex_t mutex;
2.互斥锁特点
多个线程访问共享数据的时候是串行的
3.缺点
效率低
4.互斥锁使用步骤
创建互斥锁:pthread_mutex_t mutex;
初始化锁:pthread_mutex_init(&mutex,NULL);
寻找共享资源:在操作共享资源的代码之前加锁
操作完之后解锁
5.互斥锁相关函数
初始化锁:pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);(第二个参数为互斥锁属性,一般传NULL)
销毁互斥锁:pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁:pthread_mutex_lock(pthread_mutex_t *mutex);
如果没有被上锁,则当前进程会将锁锁上;
如果已经被锁上了,则当前进程会阻塞,锁打开之后解除阻塞。
尝试加锁:pthread_mutex_trylock(pthread_mutex_t *mutex);
如果没有被锁上,则当前进程会将锁锁上;
如果已经被锁上了,当前进程不会阻塞,返回
解锁:pthread_mutex_unlock(pthread_mutex_t *mutex);
注意:如果想使用互斥锁同步线程,所以锁都需要加锁
二、造成死锁的原因
1.自己锁自己

操作完成后一定要解锁
2.

线程1对共享资源A加锁 - A锁
线程2对共享资源B加锁 - B锁
线程1访问共享资源B,会阻塞在B锁上
线程2访问共享资源A,会阻塞在A锁上
解决方法:
让线程按照一定顺序去访问共享资源,在访问其他锁时,先将自己的锁解开,或是用trylock函数
三、读写锁
1.读写锁是几把锁?
读写锁是一把锁
2.读写锁的类型
读锁:对内存做读操作
写锁:对内存做写操作
3.读写锁的特性
线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
读共享 - 并行处理
线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞
写独占 - 串行处理
线程A加写锁成功,又来了B线程做加写锁操作,阻塞。又来了C线程加读锁操作,阻塞
读写不能同时进行,写的优先级高于读
4.读写锁适用场景
互斥锁:读写串行
读写锁:读时并行,写时串行
当程序中主要为读操作时,可以使用读写锁
5.主要操作函数
初始化读写锁:pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);
销毁读写锁:pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加读锁: pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
尝试加读锁:pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
加锁成功返回0,失败返回错误号。
加写锁:pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
尝试加写锁:pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
解锁:pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
例子:3个线程不定时写同一全局变量,5个线程不定时读同一全局变量
1 #include <unistd.h> 2 #include <stdio.h> 3 #include <pthread.h> 4 5 int num = 0; 6 //定义一个读写锁 7 pthread_rwlock_t lock; 8 9 void *write_func(void *arg) 10 { 11 while(1){ 12 //在读数据之前加读锁 13 pthread_rwlock_rdlock(&lock); 14 printf("== read_thread == %ld,num = %d\n",pthread_self(),num); 15 //读取完毕后解锁 16 pthread_rwlock_unlock(&lock); 17 usleep(500); 18 } 19 } 20 21 int main() 22 { 23 pthread_t p[8]; 24 25 //初始化读写锁 26 pthread_rwlock_init(&lock,NULL); 27 28 int i; 29 //创建3个写进程 30 for(i=0;i<3;i++){ 31 pthread_create(&p[i],NULL,write_func,NULL); 32 } 33 34 //创建5个读进程 35 for(;i<8;i++){ 36 pthread_create(&p[i],NULL,read_func,NULL); 37 } 38 39 //等待进程退出 40 for(i=0;i<8;i++){ 41 pthread_join(p[i],NULL); 42 } 43 44 //释放读写锁 45 pthread_rwlock_destroy(&lock); 46 47 return 0; 48 }
四、条件变量
1.条件变量是锁吗?
条件变量不是锁,但是能够阻塞线程;
使用条件变量+互斥锁:(生产者和消费者模型)
互斥锁:保护一块共享数据
条件变量:引起阻塞
2.条件变量的两个动作
条件不满足时,线程阻塞;
条件满足时,通知阻塞线程开始工作。
3.条件变量的类型
pthread_cond_t cond;
4.主要函数
初始化一个条件变量:pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
销毁一个条件变量:pthread_cond_destroy(pthread_cond_t *cond);
阻塞等待一个条件变量:pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
使用pthread_cond_wait后的流程:阻塞线程;
将另一个已上锁的mutex解锁;
函数返回后,该函数解除阻塞,会对互斥锁加锁。
限时等待一个条件变量:pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
唤醒至少一个至少在条件变量上的线程:pthread_cond_signal(pthread_cond_t *cond);
唤醒全部阻塞在条件变量上的线程:pthread_cond_broadcast(pthread_cond_t *cond);
例:生产者和消费者模型
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 6 //节点 7 typedef struct node 8 { 9 int num; 10 struct node *next; 11 }Node; 12 13 //共享资源,头节点 14 Node *head = NULL; 15 16 //线程同步,互斥锁 17 pthread_mutex_t mutex; 18 //条件变量,阻塞线程 19 pthread_cond_t cond; 20 21 void *producer(void *arg) 22 { 23 //不断循环,创建节点 24 while(1){ 25 //创建节点,并初始化 26 Node *pnew = (Node *)malloc(sizeof(Node)); 27 pnew->num = rand()%1000; 28 29 //在使用共享资源之前加锁 30 pthread_mutex_lock(&mutex); 31 32 pnew->next = head; 33 head = pnew; 34 35 printf("----- producer ----- %ld,num = %d\n",pthread_self(),head->num); 36 37 //使用完解锁 38 pthread_mutex_unlock(&mutex); 39 40 //通知阻塞的消费者线程,解除阻塞 41 pthread_cond_signal(&cond); 42 sleep(rand()%3); 43 } 44 return NULL; 45 } 46 47 void *consumer(void *arg) 48 { 49 //不断循环,删除节点 50 while(1){ 51 //加互斥锁 52 pthread_mutex_lock(&mutex); 53 54 //如果头节点为空,则阻塞等待 55 if(head == NULL){ 56 //条件变量,阻塞线程 57 //此时会对互斥锁解锁 58 pthread_cond_wait(&cond,&mutex); 59 } 60 61 Node *pdel = head; 62 head = head->next; 63 64 printf("=== consumer === %ld,num = %d\n",pthread_self(),pdel->num); 65 66 free(pdel); 67 68 //解锁 69 pthread_mutex_unlock(&mutex); 70 sleep(rand()%3); 71 } 72 return NULL; 73 } 74 75 int main() 76 { 77 //初始化锁和条件变量 78 pthread_mutex_init(&mutex,NULL); 79 pthread_cond_init(&cond,NULL); 80 81 //创建一个生产者线程,一个消费者线程 82 pthread_t p1,p2; 83 84 pthread_create(&p1,NULL,producer,NULL); //生产者 85 pthread_create(&p2,NULL,consumer,NULL); //消费者 86 87 //回收子线程 88 pthread_join(p1,NULL); 89 pthread_join(p2,NULL); 90 91 return 0; 92 }
五、信号量(加强版互斥锁)
相比于互斥锁每次只能有一个线程在运行,信号量解决了效率低的问题,每次可以有多个线程同时运行。
1.头文件:semaphore.h
2.信号量类型:sem_t sem;
3.主要函数
初始化信号量:sem_init(sem_t *sem,int pshared,unsigned int value);
sem:要初始化的信号量;
pshared:0为线程同步,1为进程同步;
value:最多可以有几个线程操作共享数据
销毁信号量:sem_destroy(sem_t *sem);
加锁:sem_wait(sem_t *sem);
调用一次相当于对sem做了一次-1操作,如果sem为0,则线程会阻塞
尝试加锁:sem_trywait(sem_t *sem);
sem == 0,加锁失败,不会阻塞直接返回
解锁:sem_post(sem_t *sem);
调用一次相当于对sem做了一次+1操作,如果sem不为0,则线程不会阻塞
例:用信号量实现生产者和消费者模型
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 #include <semaphore.h> 6 7 //节点结构 8 typedef struct node 9 { 10 int num; 11 struct node *next; 12 }Node; 13 14 //共享资源,头节点 15 Node *head = NULL; 16 17 //生产者和消费者信号量 18 sem_t producer; 19 sem_t consumer; 20 21 void *pro(void *arg) 22 { 23 //不断循环,创建节点 24 while(1){ 25 //生产者等待创建节点,如果value>0,则开始生产 26 sem_wait(&producer); 27 28 //创建节点,并初始化 29 Node *pnew = (Node *)malloc(sizeof(Node)); 30 pnew->num = rand()%1000; 31 32 pnew->next = head; 33 head = pnew; 34 35 printf("----- producer ----- %ld,num = %d\n",pthread_self(),head->num); 36 37 //将资源给消费者 38 sem_post(&consumer); 39 40 sleep(rand()%3); 41 } 42 return NULL; 43 } 44 45 void *con(void *arg) 46 { 47 //不断循环,删除节点 48 while(1){ 49 //等待生产者生产,如果value>0,则开始消费,删除一个节点 50 sem_wait(&consumer); 51 52 Node *pdel = head; 53 head = head->next; 54 55 printf("=== consumer === %ld,num = %d\n",pthread_self(),pdel->num); 56 57 free(pdel); 58 59 //将资源给生产者 60 sem_post(&producer); 61 62 sleep(rand()%3); 63 } 64 return NULL; 65 } 66 67 int main() 68 { 69 //初始化信号量 70 sem_init(&producer,0,5); 71 sem_init(&consumer,0,0); 72 73 //创建一个生产者线程,一个消费者线程 74 pthread_t p1,p2; 75 76 pthread_create(&p1,NULL,pro,NULL); //生产者 77 pthread_create(&p2,NULL,con,NULL); //消费者 78 79 //回收子线程 80 pthread_join(p1,NULL); 81 pthread_join(p2,NULL); 82 83 //销毁信号量 84 sem_destroy(&producer); 85 sem_destroy(&consumer); 86 87 return 0; 88 }

浙公网安备 33010602011771号