zoukankan      html  css  js  c++  java
  • Linux C编程之十五 线程同步

    一、整体大纲

    二、线程同步

    1. 同步概念

        所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一

    致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致等等。

        而编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。

    2. 线程同步

    (1)线程同步概念

        同步即协同步调,按预定的先后次序运行。

        线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

        举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000

        举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执

    行结束,内存中的100字节,既不是全1,也不是全0。

        产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。

        “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。

        因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。

    (2)数据混乱原因

       1)资源共享(独享资源则不会)

       2)调度随机(意味着数据访问会出现竞争)

       3)线程间缺乏必要的同步机制。

       以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。

       所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。

    3. 实现线程同步

    (1)互斥量mutex

        Linux中提供一把互斥锁mutex(也称之为互斥量)。

        每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

        资源还是共享的,线程间也还是竞争的,

        但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

        但应注意:同一时刻,只能有一个线程持有该锁。

        当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。

        所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。

        因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。

       1)主要函数

    pthread_mutex_init函数
    pthread_mutex_destroy函数
    pthread_mutex_lock函数
    pthread_mutex_trylock函数
    pthread_mutex_unlock函数
    以上5个函数的返回值都是:成功返回0, 失败返回错误号。
    pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
    pthread_mutex_t mutex; 变量mutex只有两种取值1、0
    • pthread_mutex_init函数

        初始化一个互斥锁(互斥量) ---> 初值可看作1

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

        参1:传出参数,调用时应传 &mutex

                 restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。

        参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性

        静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g.  pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;

        动态初始化:局部变量应采用动态初始化。e.g.  pthread_mutex_init(&mutex, NULL)

    • pthread_mutex_destroy函数

        销毁一个互斥锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    • pthread_mutex_lock函数

        加锁。可理解为将 mutex--(或-1)

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    • pthread_mutex_unlock函数

        解锁。可理解为将mutex ++(或+1)

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    • pthread_mutex_trylock函数

        尝试加锁

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

        2)加锁与解锁

        lock与unlock:

        lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。

        unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。

        例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。

        可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++

        lock与trylock:

        lock加锁失败会阻塞,等待锁释放。

        trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。

     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     {
    12         pthread_mutex_lock(&mutex);
    13         printf("hello world
    ");
    14         sleep(30);
    15         pthread_mutex_unlock(&mutex);
    16     }
    17 
    18     return NULL;
    19 }
    20 
    21 int main()
    22 {
    23     pthread_mutex_init(&mutex, NULL);
    24     pthread_t tid;
    25     pthread_create(&tid, NULL, thr, NULL);
    26 
    27     sleep(1);
    28 
    29     while (1)
    30     {
    31         int ret = pthread_mutex_trylock(&mutex);
    32         if (ret > 0)
    33         {
    34             printf("ret = %d, errmsg = %s
    ", ret, strerror(ret));
    35         }
    36         sleep(1);
    37     }
    38 
    39     return 0;
    40 }
    trylock示例

        3)加锁步骤测试

        看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <pthread.h>
     4 #include <stdlib.h>
     5 
     6 void *thr1(void *arg)
     7 {
     8     while(1)
     9     {
    10         printf("hello");
    11         sleep(rand()%3);
    12         printf("world
    ");
    13         sleep(rand()%3);
    14     }
    15 }
    16 
    17 void *thr2(void *arg)
    18 {
    19     while(1)
    20     {
    21         printf("HELLO");
    22         sleep(rand()%3);
    23         printf("WORLD
    ");
    24         sleep(rand()%3);
    25     }
    26 }
    27 
    28 int main()
    29 {
    30     pthread_t thr[2];
    31     pthread_create(&thr[0], NULL, thr1, NULL);
    32     pthread_create(&thr[1], NULL, thr2, NULL);
    33 
    34     pthread_join(thr[0], NULL);
    35     pthread_join(thr[1], NULL);
    36 
    37     return 0;
    38 }
    多线程数据打印混乱

        练习:修改该程序,使用mutex互斥锁进行同步。

        定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry

        两个线程while中,两次printf前后,分别加lock和unlock

        将unlock挪至第二个sleep后,发现交替现象很难出现。

        线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。

        所以在这两行代码之间失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。

     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 void *thr1(void *arg)
     9 {
    10     while(1)
    11     {
    12         //先上锁
    13         pthread_mutex_lock(&mutex); //加锁当有线程已经枷锁的时候,阻塞
    14         printf("hello");
    15         sleep(rand()%3);
    16         printf("world
    ");
    17         //sleep(rand()%3);
    18         //解锁
    19         pthread_mutex_unlock(&mutex);
    20         sleep(rand()%3);
    21     }
    22 }
    23 
    24 void *thr2(void *arg)
    25 {
    26     while(1)
    27     {
    28         //上锁
    29         pthread_mutex_lock(&mutex);
    30         printf("HELLO");
    31         sleep(rand()%3);
    32         printf("WORLD
    ");
    33         //sleep(rand()%3);
    34         //解锁
    35         pthread_mutex_unlock(&mutex);
    36         sleep(rand()%3);
    37     }
    38 }
    39 
    40 int main()
    41 {
    42     pthread_t thr[2];
    43     pthread_create(&thr[0], NULL, thr1, NULL);
    44     pthread_create(&thr[1], NULL, thr2, NULL);
    45 
    46     pthread_join(thr[0], NULL);
    47     pthread_join(thr[1], NULL);
    48 
    49     return 0;
    50 }
    bug修复版(加互斥锁)

        结论:

        在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。

        4)死锁

    • 线程试图对同一个互斥量A加锁两次。
    • 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

        练习:编写程序,实现上述两种死锁现象。

    (2)读写锁

        1)概念

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

        2)读写锁状态

        一把读写锁具备三种状态:

        a. 读模式下加锁状态 (读锁)

        b. 写模式下加锁状态 (写锁)

        c. 不加锁状态

        3)读写锁特性

    • 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。
    • 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
    • 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高。

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

         读写锁非常适合于对数据结构读的次数远大于写的情况。

         3)主要函数

    pthread_rwlock_init函数
    pthread_rwlock_destroy函数
    pthread_rwlock_rdlock函数  
    pthread_rwlock_wrlock函数
    pthread_rwlock_tryrdlock函数
    pthread_rwlock_trywrlock函数
    pthread_rwlock_unlock函数
    以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。
    
    pthread_rwlock_t类型 用于定义一个读写锁变量。
    pthread_rwlock_t rwlock;
    • pthread_rwlock_init函数

        初始化一把读写锁

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

        参2:attr表读写锁属性,通常使用默认属性,传NULL即可。

    • pthread_rwlock_destroy函数

        销毁一把读写锁

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    • pthread_rwlock_rdlock函数

        以读方式请求读写锁。(常简称为:请求读锁)

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    • pthread_rwlock_wrlock函数

        以写方式请求读写锁。(常简称为:请求写锁)

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    • pthread_rwlock_unlock函数

        解锁

    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    • pthread_rwlock_tryrdlock函数

        非阻塞以读方式请求读写锁(非阻塞请求读锁)

    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    • pthread_rwlock_trywrlock函数

        非阻塞以写方式请求读写锁(非阻塞请求写锁)

    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

        4)读写锁示例

        看如下示例,同时有多个线程对同一全局数据读、写操作。

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

        读写锁场景练习:

        a. 线程A加写锁成功,线程B请求读锁
            线程B阻塞

        b. 线程A持有读锁,线程B请求写锁
            线程B阻塞

        c. 线程A持有读锁,线程B请求读锁
            B加锁成功

        d. 线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
           BC阻塞
           A释放后,B加锁
           B释放后,C加锁

        e. 线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
           BC阻塞
           A释放,C加锁
           C释放,B加锁

    (3)条件变量

        1)概念

        条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

        2)主要应用函数

    pthread_cond_init函数
    pthread_cond_destroy函数
    pthread_cond_wait函数
    pthread_cond_timedwait函数
    pthread_cond_signal函数
    pthread_cond_broadcast函数
    以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
    
    pthread_cond_t类型 用于定义条件变量
    pthread_cond_t cond;
    • pthread_cond_init函数

        初始化一个条件变量

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

        参2:attr表条件变量属性,通常为默认值,传NULL即可

        也可以使用静态初始化的方法,初始化条件变量:

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    • pthread_cond_destroy函数

        销毁一个条件变量

    int pthread_cond_destroy(pthread_cond_t *cond);
    • pthread_cond_wait函数

        阻塞等待一个条件变量

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

        函数作用:

        a. 阻塞等待条件变量cond(参1)满足

        b. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
            a.b.两步为一个原子操作。

        c. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

    • pthread_cond_timedwait函数

        限时等待一个条件变量

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

        参3: 参看man sem_timedwait函数,查看struct timespec结构体。

    struct timespec {
        time_t tv_sec; /* seconds */long   tv_nsec; /* nanosecondes*/ 纳秒
    }

        形参abstime:绝对时间。

        如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。

        struct timespec t = {1, 0};

        pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去) 

        正确用法:

    time_t cur = time(NULL); 获取当前时间。
    struct timespec t; 定义timespec 结构体变量t
    t.tv_sec = cur+1; 定时1秒
    
    pthread_cond_timedwait (&cond, &mutex, &t); 传参 参APUE.11.6线程同步条件变量小节
    
    在讲解setitimer函数时我们还提到另外一种时间类型:
    struct timeval {
        time_t      tv_sec;  /* seconds */ 秒
        suseconds_t tv_usec; /* microseconds */ 微秒
    };
    • pthread_cond_signal函数

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

    int pthread_cond_signal(pthread_cond_t *cond);
    • pthread_cond_broadcast函数

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

    int pthread_cond_broadcast(pthread_cond_t *cond);

        3)生产者消费者条件变量模型

        线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共

    享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。

        看如下示例,使用条件变量模拟生产者、消费者问题:

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

        4)条件变量的优点

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

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

        生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

    (4)信号量

        1)概念

        进化版的互斥锁(1 --> N)

        由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数

    据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。

        信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

        2)主要应用函数

    sem_init函数
    sem_destroy函数
    sem_wait函数
    sem_trywait函数
    sem_timedwait函数
    sem_post函数
    以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。(注意,它们没有pthread前缀)
    
    sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。
    
    sem_t sem; 规定信号量sem不能 < 0。头文件 <semaphore.h>

        信号量基本操作:

        sem_wait:

                     a. 信号量大于0,则信号量-- (类比pthread_mutex_lock)

                     b. 信号量等于0,造成线程阻塞

        对应 ->

        sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程 (类比pthread_mutex_unlock)

        但由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。

        信号量的初值,决定了占用信号量的线程的个数。

    • sem_init函数

        初始化一个信号量

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

        参1:sem信号量

        参2:pshared取0用于线程间;取非0(一般为1)用于进程间

        参3:value指定信号量初值

    • sem_destroy函数

        销毁一个信号量

    int sem_destroy(sem_t *sem);
    • sem_wait函数

        给信号量加锁 --

    int sem_wait(sem_t *sem);
    • sem_post函数

        给信号量解锁 ++

    int sem_post(sem_t *sem);
    • sem_trywait函数

        尝试对信号量加锁 -- (与sem_wait的区别类比lock和trylock)

     int sem_trywait(sem_t *sem);
    • sem_timedwait函数

        限时尝试对信号量加锁 --

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

        参2:abs_timeout采用的是绝对时间。

    定时1秒:
    time_t cur = time(NULL); 获取当前时间。
    struct timespec t; 定义timespec 结构体变量t
    t.tv_sec = cur+1; 定时1秒
    t.tv_nsec = t.tv_sec +100;
    sem_timedwait(&sem, &t); 传参

       3)生产者消费者信号量模型

        使用信号量完成线程间同步,模拟生产者,消费者问题。

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

    (5)进程间同步

        互斥量mutex

        进程间也可以使用互斥锁,来达到同步的目的。但应在pthread_mutex_init初始化之前,修改其属性为进程间共享。mutex的属性修改函数主要有以下几个。

        主要应用函数:

    pthread_mutexattr_t mattr 类型: 用于定义mutex锁的【属性】
    pthread_mutexattr_init函数: 初始化一个mutex属性对象
    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    pthread_mutexattr_destroy函数: 销毁mutex属性对象 (而非销毁锁)
    int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
    pthread_mutexattr_setpshared函数: 修改mutex属性。
    int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
    参2:pshared取值:
    
    线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
    进程锁:PTHREAD_PROCESS_SHARED

        进程间mutex示例:

     1 #include <fcntl.h>
     2 #include <pthread.h>
     3 #include <sys/mman.h>
     4 #include <sys/wait.h>
     5 
     6 struct mt{
     7     int num;
     8     pthread_mutex_t mutex;
     9     pthread_mutexattr_t mutexattr;
    10 };
    11 
    12 int main(void)
    13 {
    14     int fd, i;
    15     struct mt *mm;
    16     pid_t pid;
    17 
    18     fd = open("mt_test", O_CREAT | O_RDWR, 0777);
    19     ftruncate(fd, sizeof(*mm));
    20     mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    21     close(fd);
    22     unlink("mt_test");
    23     //mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    24     memset(mm, 0, sizeof(*mm));
    25     pthread_mutexattr_init(&mm->mutexattr);                                  //初始化mutex属性对象
    26     pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED);    //修改属性为进程间共享
    27     pthread_mutex_init(&mm->mutex, &mm->mutexattr);                          //初始化一把mutex琐
    28     pid = fork();
    29     if (pid == 0) {
    30         for (i = 0; i < 10; i++) {
    31             pthread_mutex_lock(&mm->mutex);
    32             (mm->num)++;
    33             printf("-child----num++   %d
    ", mm->num);
    34             pthread_mutex_unlock(&mm->mutex);
    35             sleep(1);
    36         }
    37     } else if (pid > 0) {
    38         for ( i = 0; i < 10; i++) {
    39             sleep(1);
    40             pthread_mutex_lock(&mm->mutex);
    41             mm->num += 2;
    42             printf("-parent---num+=2  %d
    ", mm->num);
    43             pthread_mutex_unlock(&mm->mutex);
    44         }
    45         wait(NULL);
    46     }
    47 
    48     pthread_mutexattr_destroy(&mm->mutexattr);          //销毁mutex属性对象
    49     pthread_mutex_destroy(&mm->mutex);                //销毁mutex
    50     munmap(mm,sizeof(*mm));                          //释放映射区
    51 
    52     return 0;
    53 } 
    进程间使用mutex来实现通信

    (6)文件锁

        借助 fcntl函数来实现锁机制。 操作文件的进程没有获得锁时,可以打开,但无法执行read、write操作。

        fcntl函数: 获取、设置文件访问控制属性。

    int fcntl(int fd, int cmd, ... /* arg */ );

        参2:

    F_SETLK (struct flock *) 设置文件锁(trylock)
    F_SETLKW (struct flock *) 设置文件锁(lock)W --> wait
    F_GETLK (struct flock *) 获取文件锁

       参3:

    struct flock {
        ...
        short l_type;     锁的类型:F_RDLCK 、F_WRLCK 、F_UNLCK
        short l_whence;   偏移位置:SEEK_SET、SEEK_CUR、SEEK_END
        off_t l_start;    起始偏移:1000
        off_t l_len;      长度:0表示整个文件加锁
        pid_t l_pid;      持有该锁的进程ID:(F_GETLK only)
        ...
    };

        进程间文件锁示例:

     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__ "/opt/linuxC/09-linux-day09/temp.lock"
     9 
    10 int main()
    11 {
    12     int fd = open(__FILE_NAME__, O_RDWR|O_CREAT, 0666);
    13     if (fd < 0)
    14     {
    15         perror("open err:");
    16         return -1;
    17     }
    18 
    19     struct flock lk;
    20     lk.l_type = F_WRLCK;
    21     lk.l_whence = SEEK_SET;
    22     lk.l_start = 0;
    23     lk.l_len = 0;
    24 
    25     if (fcntl(fd, F_SETLK, &lk) < 0)
    26     {
    27         perror("get lock err:");
    28         exit(1);
    29     }
    30     //核心逻辑
    31     while(1)
    32     {
    33         printf("I am alive!
    ");
    34         sleep(1);
    35     }
    36     return 0;
    37 }
    进程间文件锁

        依然遵循“读共享、写独占”特性。但!如若进程不加锁直接操作文件,依然可访问成功,但数据势必会出现混乱。

      【思考】:多线程中,可以使用文件锁吗?

        多线程间共享文件描述符,而给文件加锁,是通过修改文件描述符所指向的文件结构体中的成员变量来实现的。因此,多线程中无法使用文件锁。

  • 相关阅读:
    MongoDB 释放磁盘空间 db.runCommand({repairDatabase: 1 })
    RK 调试笔记
    RK Android7.1 拨号
    RK Android7.1 移植gt9271 TP偏移
    RK Android7.1 定制化 itvbox 盒子Launcher
    RK Android7.1 双屏显示旋转方向
    RK Android7.1 设置 内存条作假
    RK Android7.1 设置 蓝牙 已断开连接
    RK Android7.1 进入Camera2 亮度会增加
    RK 3128 调触摸屏 TP GT9XX
  • 原文地址:https://www.cnblogs.com/xuejiale/p/10822896.html
Copyright © 2011-2022 走看看