在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
09-linux-day09(线程同步)
一、内容回顾
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_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\n");
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\n");
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
》尝试加锁
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\n");
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\n",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、死锁
》死锁
锁了又锁,自己加了一次锁成功,又加了一次锁。
交叉锁(解决办法:每个线程申请锁的顺序要一致。如果申请到一把锁,申请另外一把的时候申请失败,应该释放已经掌握的。)
注意:互斥量只是建议锁。
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;
4、条件变量介绍和生产者和消费者模型
5、条件变量生产者消费者模型实现
6、条件变量生产者和消费者模型演示
7、信号量的概念和函数
8、信号量实现生产者和消费者分析
9、信号量实现生产者和消费者
10、文件锁单开进程
11、哲学家就餐模型分析