Linux系统编程——线程同步

在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

09-linux-day09(线程同步)

目录:
一、内容回顾
二、学习目标
三、线程同步
1、互斥量的使用
2、死锁
3、读写锁
4、条件变量介绍-生产者和消费者模型
5、条件变量生产者消费者模型实现
6、条件变量生产者和消费者模型演示
7、信号量的概念和函数
8、信号量实现生产者和消费者分析
9、信号量实现生产者和消费者
10、文件锁单开进程
11、哲学家就餐模型分析

一、内容回顾

1、守护进程:运行在后台,脱离终端,周期性执行某些任务

会话:多个进程组组成一个会话,组长进程不能创建会话。

进程组:多个进程在同一个组,第一个进程默认是组长

守护进程的步骤:

(1)创建子进程,父亲退出(孤儿进程)

(2)创建会话,当会长

(3)切换目录

(4)设置掩码

(5)关闭文件描述符

(6)执行核心逻辑

(7)退出

nohup & 把进程放入后台运行

2、多线程:

线程是轻量级的进程,最小的执行单位,进程是最小的资源申请单位。一个进程里可以有多个线程。

创建线程 pthread_create

回收线程 pthred_join

线程退出 pthread_exit void *retval

杀死线程 pthread_cancel 取消点

线程分离 pthread_detach,也可以通过属性设置

pthread_attr_setdetachstate 设置属性分离,之前需要pthread_attr_init初始化,之后需要pthread_attr_destroy销毁

查看线程ID:pthread_self 在进程内唯一

二、学习目标

1、熟练掌握互斥量的使用

2、说出什么叫死锁以及解决方案

3、熟练掌握读写死锁的使用

4、熟练掌握条件变量的使用

5、理解条件变量实现的生产者消费者模型

6、理解信号量实现的生产者消费者模型

三、线程同步

1、互斥量的使用

》互斥量

两个线程访问同一块共享资源,如果不协调顺序,容易造成数据混乱。

>加锁 mutex

pthread_mutex_init 初始化

或:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_destroy 摧毁

pthread_mutex_lock 加锁

pthread_mutex_unlock(pthread_mutex_t *mutex) 解锁

》互斥量的使用步骤:

1)初始化

2)加锁

3)执行逻辑——操作共享数据

4)解锁

注意事项:

  加锁需要最小力度,不要一直占用临界区。

解决昨天的问题——模拟线程共同抢占(屏幕)资源

>vi pthread_print.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 7 
 8  9 
10 void* thr1(void *arg){
11     while(1){
12         //先上锁
13         pthread_mutex_lock(&mutex);//加锁:当有线程已经加锁的时候会阻塞
14         //临界区
15         printf("hello");//不带换行,没有行缓冲,输出不出来
16         sleep(rand()%3);
17         printf("world
");
18         //释放锁
19         pthread_mutex_unlock(&mutex);
20         sleep(rand()%3);
21     }
22 }
23 
24 void* thr2(void *arg){
25     while(1){
26         //先上锁
27         pthread_mutex_lock(&mutex);
28         //临界区
29         printf("HELLO");
30         sleep(rand()%3);
31         printf("WORLD
");
32         //释放锁
33         pthread_mutex_unlock(&mutex);
34         sleep(rand()%3);
35     }
36 }
37 
38 int main(int argc, char *argv[])
39 {
40     //创建两个线程,回收两次
41     pthread_t tid[2];
42     pthread_create(&tid[0],NULL,thr1,NULL);
43     pthread_create(&tid[1],NULL,thr2,NULL);
44     
45     pthread_join(tid[0],NULL);
46     pthread_join(tid[1],NULL);
47     
48     return 0;
49 }

>make

>./pthread_print

解决昨天的问题——模拟线程共同抢占(缓冲区)资源

>vi thr_write

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 #include<sys/types.h>
 6 #include<sys/stat.h>
 7 #include<fcntl.h>
 8 #include<string.h>
 9 
10 pthread_mutex_t mutex;
11 char buf[20];
12 
13 void* thr1(void *arg){
14     int i = 0;
15     pthread_mutex_lock(&mutex);
16     for(;i < 20; i++){
17         usleep(rand()%3);
18         buf[i] = '0';
19     }
20     pthread_mutex_unlock(&mutex);
21     return NULL;
22 }
23 
24 void* thr2(void *arg){
25     int i = 0;
26     pthread_mutex_lock(&mutex);
27     for(;i < 20; i++){
28         usleep(rand()%3);
29         buf[i] = '1';
30     }
31     pthread_mutex_unlock(&mutex);
32     return NULL;
33 }
34 
35 int main(int argc, char *argv[])
36 {
37     //创建两个线程,回收两次
38     memset(buf,0x00,sizeof(buf));
39     
40     pthread_mutex_init(&mutex,NULL);
41     
42     pthread_t tid[2];
43     pthread_create(&tid[0],NULL,thr1,NULL);
44     pthread_create(&tid[1],NULL,thr2,NULL);
45     
46     pthread_join(tid[0],NULL);
47     pthread_join(tid[1],NULL);
48     printf("buf is %s
",buf);
49     
50     pthread_mutex_destroy(&mutex);
51     
52     return 0;
53 }

>make

>./thr_write

》尝试加锁

man pthread_mutex_trylock

int pthread_mutex_trylock(pthread_mutex_t *mutex);

测试(已经加锁的再次尝试加锁结果会怎么样?

>touch mutex_trylock.c

>vi mutex_trylock.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<string.h>
 5 
 6 pthread_mutex_t mutex;
 7 
 8 void *thr(void *arg)
 9 {
10     while(1){
11         pthread_mutex_lock(&mutex);
12         printf("hello world
");
13         sleep(30);
14         pthread_mutex_unlock(&mutex);
15     }
16     return NULL;
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     pthread_mutex_init(&mutex,NULL);
22     pthread_t tid;
23     pthread_create(&tid,NULL,thr,NULL);
24     sleep(1);
25     while(1){
26         int ret = pthread_mutex_trylock(&mutex);
27         if(ret > 0){
28             printf("ret = %d,errmmsg:%s
",ret,strerror(ret));
29         }
30         sleep(1);
31     }
32     
33     return 0;
34 }

>make

>./mutex_trylock

(打印完hellow world后,一直打印:ret = 16,errmsg:Device or resource busy)

可以在以下文件查看errno的错误信息

2、死锁

》死锁

  1)锁了又锁,自己加了一次锁成功,又加了一次锁。

   2)交叉锁(解决办法:每个线程申请锁的顺序要一致。如果申请到一把锁,申请另外一把的时候申请失败,应该释放已经掌握的。)

注意:互斥量只是建议锁。

3、读写锁

》读写锁

  与互斥量类似,但读写锁允许更高的并行性。其特点为:读共享,写独占,写优先级高。

》读写锁状态:

三种状态:

  1)读模式下加锁状态(读锁

  2)写模式下加锁状态(写锁

  3)不加锁状态

》读写锁特性:

1)读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。

2)读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。

3)读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。读共享,写独占。

》读写锁的使用场景:非常适合于对数据结构读的次数远大于写的情况。

》读写锁场景练习:

1)线程A加写锁成功,线程B请求读锁

  B阻塞

2)线程A持有读锁,线程B请求写锁

  B阻塞

3)线程A拥有读锁,线程B请求读锁

  B加锁成功

4)线程A持有读锁,然后线程B请求写锁,然后线程C请求写锁

  BC阻塞;A释放后,B加锁;B释放后,C加锁

5)线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁

  BC阻塞;A释放后,C加锁;C释放后,B加锁

》初始化:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

》销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

》加读锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

》加写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

》释放锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

练习:

>touch rwlock.c

>vi rwlock.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 
 5 //初始化
 6 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
 7 int beginnum = 1000;
 8 
 9 void *thr_write(void *arg)
10 {
11     while(1){
12         pthread_rwlock_wrlock(&rwlock);
13         printf("---%s---self---%lu---beginnum---%d
",__FUNCTION__,pthread_self(),++beginnum);
14         usleep(2000);//模拟占用时间2ms
15         pthread_rwlock_unlock(&rwlock);
16         usleep(4000);//防止再次抢去
17     }
18     return NULL;
19 }
20 
21 void *thr_read(void *arg)
22 {
23     while(1){
24         pthread_rwlock_rdlock(&rwlock);
25         printf("---%s---self---%lu---beginnum---%d
",__FUNCTION__,pthread_self(),beginnum);
26         usleep(2000);//模拟占用时间2ms
27         pthread_rwlock_unlock(&rwlock);
28         usleep(2000);//防止再次抢去
29     }
30     return NULL;
31 }
32 
33 int main(int argc, char *argv[])
34 {
35     int n = 8,i = 0;
36     pthread_t tid[8];
37     //5-read, 3-write
38     for(i = 0; i < 5; i++){
39         pthread_create(&tid[i],NULL,thr_read,NULL);
40     }
41     for(;i < 8; i++){
42         pthread_create(&tid[i],NULL,thr_write,NULL);
43     }
44     
45     for(i = 0; i < 8; i++){
46         pthread_join(tid[i],NULL);
47     }
48     
49     return 0;
50 }

>make

>./rwlock

4、条件变量介绍-生产者和消费者模型

条件变量不是锁,要和互斥量组合使用!

》条件变量:可以引起阻塞,并非锁

竞争者可以阻塞在是否有饼这个条件上?

》条件变量的优点:

相对于mutex而言,条件变量可以减少竞争。

如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

》初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

》销毁

int pthread_cond_destroy(pthread_cond_t *cond);

》阻塞等待

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

  先释放锁mutex;然后阻塞在cond条件变量上。

》限时(超时)等待

int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

struct timespec{

  time_t tc_sec; /*seconds*/ 秒

  long tv_nsec;/*nanoseconds*/纳秒

};

tv_sec 绝对时间,填写的时候 time(NULL) + 600 ---> 设置超时600s

》唤醒至少一个阻塞在条件变量cond上的线程

int pthread_cond_signal(pthread_cond_t *cond);

》唤醒阻塞在条件变量cond上的全部线程

int pthread_cond_broadcast(pthread_cond_t *cond);

5、条件变量生产者消费者模型实现

》条件变量的作用:避免无必要的竞争

练习:(先模拟一个生产者,一个消费者)

>touch cond_product.c

>vi cond_product.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 //初始化
 7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 8 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 9 
10 int beginnum = 1000;
11 
12 typedef struct _ProInfo{
13     int num;
14     struct _ProInfo *next;
15 }ProInfo;
16 
17 ProInfo *Head = NULL;
18 
19 void *thr_producter(void *arg)
20 {
21     //负责在链表添加数据
22     while(1){
23         ProInfo *prod = malloc(sizeof(ProInfo));
24         prod->num = beginnum++;
25         printf("---%s---self=%lu---%d
",__FUNCTION__,pthread_self(),prod->num);
26         pthread_mutex_lock(&mutex);
27         //add to list
28         prod->next = Head;
29         Head = prod;
30         pthread_mutex_unlock(&mutex);
31         //发起通知
32         pthread_cond_signal(&cond);
33         sleep(rand()%4);
34     }
35     return NULL;
36 }
37 
38 void *thr_customer(void *arg)
39 {
40     ProInfo *prod = NULL;
41     while(1){
42         //取链表的数据
43         pthread_mutex_lock(&mutex);
44         if(Head == NULL){
45             pthread_cond_wait(&cond,&mutex);//在此之前必须先加锁
46         }
47         prod = Head;
48         Head = Head->next;
49         printf("---%s---self=%lu---%d
",__FUNCTION__,pthread_self(),prod->num);
50         pthread_mutex_unlock(&mutex);
51         sleep(rand()%4);
52         free(prod);
53     }
54     return NULL;
55 }
56 
57 
58 int main(int argc, char *argv[])
59 {
60     pthread_t tid[2];
61     pthread_create(&tid[0],NULL,thr_producter,NULL);
62     pthread_create(&tid[1],NULL,thr_customer,NULL);
63     
64     pthread_join(tid[0],NULL);
65     pthread_join(tid[1],NULL);
66     
67     pthread_mutex_destroy(&mutex);
68     pthread_cond_destroy(&cond);
69     
70     return 0;
71 }

>make

>./cond_product

(问题:如果消费者增多,消费者判断可以消费,都进入,可能消费者都卡在阻塞等待那)

6、条件变量生产者和消费者模型演示

解决:

>vi cond_product.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 //初始化
 7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 8 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 9 
10 int beginnum = 1000;
11 
12 typedef struct _ProInfo{
13     int num;
14     struct _ProInfo *next;
15 }ProInfo;
16 
17 ProInfo *Head = NULL;
18 
19 void *thr_producter(void *arg)
20 {
21     //负责在链表添加数据
22     while(1){
23         ProInfo *prod = malloc(sizeof(ProInfo));
24         prod->num = beginnum++;
25         printf("---%s---self=%lu---%d
",__FUNCTION__,pthread_self(),prod->num);
26         pthread_mutex_lock(&mutex);
27         //add to list
28         prod->next = Head;
29         Head = prod;
30         pthread_mutex_unlock(&mutex);
31         //发起通知
32         pthread_cond_signal(&cond);
33         sleep(rand()%2);
34     }
35     return NULL;
36 }
37 
38 void *thr_customer(void *arg)
39 {
40     ProInfo *prod = NULL;
41     while(1){
42         //取链表的数据
43         pthread_mutex_lock(&mutex);
44         while(Head == NULL){//if更改为while
45             pthread_cond_wait(&cond,&mutex);//在此之前必须先加锁
46         }
47         prod = Head;
48         Head = Head->next;
49         printf("---%s---self=%lu---%d
",__FUNCTION__,pthread_self(),prod->num);
50         pthread_mutex_unlock(&mutex);
51         sleep(rand()%4);
52         free(prod);
53     }
54     return NULL;
55 }
56 
57 
58 int main(int argc, char *argv[])
59 {
60     pthread_t tid[3];
61     pthread_create(&tid[0],NULL,thr_producter,NULL);
62     pthread_create(&tid[1],NULL,thr_customer,NULL);
63     pthread_create(&tid[2],NULL,thr_customer,NULL);
64     
65     pthread_join(tid[0],NULL);
66     pthread_join(tid[1],NULL);
67     pthread_join(tid[2],NULL);
68     
69     pthread_mutex_destroy(&mutex);
70     pthread_cond_destroy(&cond);
71     
72     return 0;
73 }

>make

>./cond_product

7、信号量的概念和函数

》信号量:加强版(进化版)的互斥锁,允许多个线程访问共享资源

以上6个函数的返回值都是:成功返回0,失败返回-1,同时设置errno。

注意:它们没有pthread前缀!

sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。

sem_t sem; 规定信号量sem不能 < 0。头文件<semaphore.h>

》初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);

  sem 定义的信号量,传出

  pshared:0代表线程信号量;非0代表进程信号量

  value:定义信号量的个数

》摧毁信号量

int sem_destroy(sem_t *sem);

》申请信号量,申请成功value--

int sem_wait(sem_t *sem);

  当信号量为0时,阻塞

》释放信号量 value++

int sem_post(sem_t *sem);

8、信号量实现生产者和消费者分析

9、信号量实现生产者和消费者

>touch sem_product.c

>vi sem_product.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<semaphore.h>
 5 #include<stdlib.h>
 6 
 7 sem_t blank,xfull;
 8 #define _SEM_CNT_ 5
 9 
10 int queue[_SEM_CNT_];//模拟饼筐
11 int beginnum = 100;
12 
13 void *thr_producter(void *arg)
14 {
15     int i = 0;
16     while(1){
17         sem_wait(&blank);//申请资源 blank--
18         printf("---%s---self=%lu---num=%d
",__FUNCTION__,pthread_self(),beginnum);
19         queue[(i++)%_SEM_CNT_] = beginnum++;
20         sem_post(&xfull);//xfull++
21         sleep(rand()%3);
22     }
23     return NULL;
24 }
25 
26 void *thr_customer(void *arg)
27 {
28     int i = 0;
29     int num = 0;
30     while(1){
31         sem_wait(&xfull);
32         num = queue[(i++)%_SEM_CNT_];
33         printf("---%s---self=%lu---num=%d
",__FUNCTION__,pthread_self(),num);
34         sem_post(&blank);
35         sleep(rand()%3);
36     }
37     return NULL;
38 }
39 
40 int main(int argc, char *argv[])
41 {
42     sem_init(&blank,0,_SEM_CNT_);
43     sem_init(&xfull,0,0);//消费者一开始的初始化默认没有产品
44     
45     pthread_t tid[2];
46     
47     pthread_create(&tid[0],NULL,thr_producter,NULL);
48     pthread_create(&tid[1],NULL,thr_customer,NULL);
49     
50     pthread_join(tid[0],NULL);
51     pthread_join(tid[1],NULL);
52     
53     sem_destroy(&blank);
54     sem_destroy(&xfull);
55     
56     return 0;
57 }

>make

>./sem_product

10、文件锁单开进程

》互斥量mutex

》文件锁

>touch flock.c

>vi flock.c

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<sys/types.h>
 4 #include<sys/stat.h>
 5 #include<fcntl.h>
 6 #include<stdlib.h>
 7 
 8 #define _FILE_NAME_ "/home/wang/temp.lock"
 9 
10 int main(int argc, char *argv[])
11 {
12     int fd = open(_FILE_NAME_,O_RDWR | O_CREAT,0666);
13     if(fd < 0){
14         perror("open err");
15         return -1;
16     }
17     struct flock lk;
18     lk.l_type = F_WRLCK;
19     lk.l_whence = SEEK_SET;
20     lk.l_start = 0;
21     lk.l_len = 0;
22     
23     if(fcntl(fd,F_SETLK,&lk) < 0){
24         perror("get lock err");
25         exit(1);
26     }
27     
28     //核心逻辑
29     while(1){
30         printf("I am alive!
");
31         sleep(1);
32     }
33     
34     return 0;
35 }

>make

>./flock

(打开另一个终端,运行./flock,提示:get lock err: Resource temporarily unavailable,说明文件已被加锁,只能启动一个进程了。)

11、哲学家就餐模型分析

作业

在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_09.html