zoukankan      html  css  js  c++  java
  • 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,转载请注明出处。

  • 相关阅读:
    mybatis 基于xml 配置的映射器
    【☆】素数打表--重要
    HDU--1084 What Is Your Grade?
    Bear and Three Balls
    【水题】HDU--1280 前m大的数
    HDU--1234 开门人和关门人
    HDU--1862 EXCEL排序
    HDU--1872 稳定排序
    聪明人的游戏,初中版 之目录
    SzNOI语法百题之1-10
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_09.html
Copyright © 2011-2022 走看看