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,转载请注明出处。

  • 相关阅读:
    hdu 5387 Clock (模拟)
    CodeForces 300B Coach (并查集)
    hdu 3342 Legal or Not(拓扑排序)
    hdu 3853 LOOPS(概率DP)
    hdu 3076 ssworld VS DDD(概率dp)
    csu 1120 病毒(LICS 最长公共上升子序列)
    csu 1110 RMQ with Shifts (线段树单点更新)
    poj 1458 Common Subsequence(最大公共子序列)
    poj 2456 Aggressive cows (二分)
    HDU 1869 六度分离(floyd)
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_09.html
Copyright © 2011-2022 走看看