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\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
解决昨天的问题——模拟线程共同抢占(缓冲区)资源
>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\n",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\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、死锁
》死锁
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\n",__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\n",__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\n",__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\n",__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\n",__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\n",__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\n",__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\n",__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!\n"); 31 sleep(1); 32 } 33 34 return 0; 35 }
>make
>./flock
(打开另一个终端,运行./flock,提示:get lock err: Resource temporarily unavailable,说明文件已被加锁,只能启动一个进程了。)
11、哲学家就餐模型分析
作业
在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
posted on 2020-07-04 17:22 Alliswell_WP 阅读(208) 评论(0) 编辑 收藏 举报