线程同步

  由于在一个进程中,各个线程都可以操作共享数据,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 }

 

posted @ 2021-02-22 13:06  さくらむすび  阅读(69)  评论(0)    收藏  举报