zoukankan      html  css  js  c++  java
  • Linux线程同步方法

    多线程已经成为服务器开发不可或缺的重要知识点了,那么怎样协调各个线程之间的工作就变得至关重要,于是这篇文章就来总结一下线程同步的方法。

    什么是线程同步?

    “同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。

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

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

    那么怎样达到线程同步就需要一些方法,这里讲解了互斥量,条件变量,信号量线程同步方法。

    互斥锁mutex

    互斥锁的原理很容易理解,我们给一个共享资源分配一把锁,每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

    通过这样:资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。但,应注意:同一时刻,只能有一个线程持有该锁。

    我们先用一个用互斥量使用步骤开始讲解:

    1,pthread_mutex_t 函数 ,创造一个互斥锁。

    2,pthread_mutex_init 函数,初始化。

    3,pthread_mutex_lock / pthread_mutex_trylock 函数,加锁。

    4,访问共享数据

    5,pthread_mutex_unlock 函数,解锁
    6,pthread_mutex_destroy 函数,销毁锁


    pthread_mutex_t  mutex;

    没啥好说的,就是创造一个互斥锁mutex。

    pthread_mutex_init 函数

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);  函数作用是:初始化一个互斥锁(互斥量) ---> 初值可看作 1

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

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

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

    pthread_mutex_lock 函数
    int pthread_mutex_lock(pthread_mutex_t *mutex);  函数作用是:加锁。可理解为将 mutex--(或 -1),操作后 mutex 的值为 0。

    另外,lock与trylock的区别是:lock加锁失败会导致阻塞,trylock加锁失败不会阻塞会返回-1 。

    pthread_mutex_unlock 函数

    int pthread_mutex_unlock(pthread_mutex_t *mutex);  解锁。可理解为将 mutex ++(或 +1),操作后 mutex 的值为 1。

    pthread_mutex_destroy 函数
    int pthread_mutex_destroy(pthread_mutex_t *mutex);  销毁一个互斥锁

    lock 与 unlock :

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

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

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<pthread.h>
     4 #include<stdlib.h>
     5 #include<unistd.h>
     6 
     7 //互斥锁设为全局变量
     8 pthread_mutex_t mutex;
     9 
    10 //子线程
    11 void *tfn(void *arg) {
    12     srand(time(NULL));
    13     while (1) {
    14         pthread_mutex_lock(&mutex);    //加锁,不行则阻塞
    15         printf("hello ");
    16         printf("world
    ");
    17         pthread_mutex_unlock(&mutex);
    18         sleep(rand()%3);
    19     }
    20     return NULL;
    21 }
    22 
    23 int main()
    24 {
    25     pthread_t tid;
    26     srand(time(NULL));
    27     
    28     pthread_mutex_init(&mutex,NULL);    //初始化互斥锁
    29 
    30     pthread_create(&tid,NULL,tfn,NULL);    //创造子线程
    31 
    32     //主线程
    33     while (1) {
    34         pthread_mutex_lock(&mutex);    //互斥锁加锁,不行则阻塞
    35         printf("HELLO ");
    36         printf("WORLD
    ");
    37         pthread_mutex_unlock(&mutex);    //互斥锁解锁
    38         sleep(rand()%3);
    39     }
    40     
    41     //最后记得回收子线程,销毁互斥锁
    42     pthread_join(tid,NULL);
    43     pthread_mutex_destroy(&mutex);
    44     return 0;
    45 }
    互斥锁测试

    互斥锁读写锁

    条件变量

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

    函数类似互斥锁,我们也从条件变量使用步骤开始:

    1,pthread_cond_t 函数, 用于定义条件变量

    2,pthread_cond_init 函数

    3,pthread_cond_wait  /  pthread_cond_timedwait 函数

    4,pthread_cond_signal  /  pthread_cond_broadcast 函数

    5,唤醒之后,重新申请互斥锁,然后操作共享资源

    6,pthread_cond_destroy 函数

    pthread_cond_init 函数

    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);  初始化一个条件变量

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

     

    pthread_cond_wait 函数
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);  阻塞等待一个条件变量
    函数作用:
      1. 阻塞等待条件变量 cond(参 1)满足
      2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
        注意:1 2. 两步为 一个 原子操作。
      3. 当被唤醒,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);  限时等待一个条件变量

    与pthread_cond_wait函数相比就是多了struct timespec这个结构体:

    struct timespec {
    time_t tv_sec;   /* seconds */ 秒
    long tv_nsec;   /* nanosecondes*/ 纳秒 ,注意这里是纳秒,timeval这个结构体这里是微秒
    }

    并且要注意这里的abstime是绝对时间,所以正确用法是:

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

    pthread_cond_signal 函数
    int pthread_cond_signal(pthread_cond_t *cond);  唤醒至少一个阻塞在条件变量上的线程

    tpthread_cond_broadcast 函数
    int pthread_cond_broadcast(pthread_cond_t *cond);  唤醒全部阻塞在条件变量上的线程

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<string.h>
     4 #include<unistd.h>
     5 #include<errno.h>
     6 #include<pthread.h>
     7 
     8 //线程不能perror,有自己独特的错误处理方式
     9 void error_thread(int ret,char *str) {
    10     if (ret!=0) {
    11         fprintf(stderr,"%s:%s
    ",str,strerror(ret));
    12         pthread_exit(NULL);
    13     }
    14 }
    15 
    16 //把生产的物品做成链表
    17 struct msg{
    18     int num;
    19     struct msg *next;
    20 };
    21 struct msg *head;
    22 
    23 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;    //互斥量
    24 pthread_cond_t has_data=PTHREAD_COND_INITIALIZER;    //条件变量
    25 
    26 //生产者线程
    27 void *producer(void *arg) {
    28     while (1) {
    29         //生产者生产一个数据
    30         struct msg *mp=malloc(sizeof(struct msg));
    31         mp->num = rand()%1000+1;
    32         printf("--produce %d
    ",mp->num);
    33         
    34         //利用互斥量对公共区域做互斥
    35         pthread_mutex_lock(&mutex);    //加互斥锁
    36         mp->next=head;            //写公共区域
    37         head=mp;
    38         pthread_mutex_unlock(&mutex);    //解互斥锁
    39 
    40         //条件变量:唤醒阻塞在条件变量has_data的线程
    41         pthread_cond_signal(&has_data);
    42 
    43         sleep(rand()%3);
    44     }
    45     return NULL;
    46 }
    47 
    48 //消费者线程
    49 void *consumer(void *arg) {
    50     while (1) {
    51         struct msg *mp;
    52 
    53         //
    54         pthread_mutex_lock(&mutex);
    55         while (head==NULL) 
    56             pthread_cond_wait(&has_data,&mutex);
    57 
    58         //消费生产品,读写公共区域
    59         mp=head;
    60         head=mp->next;
    61         printf("-------------consumer id %lu :%d
    ",pthread_self(),mp->num);
    62         free(mp);
    63         
    64         sleep(rand()%3);
    65     }
    66     return NULL;
    67 }
    68 
    69 int main(int argc,char *argv[]) {
    70     int ret;
    71     pthread_t pid,cid;
    72 
    73     srand(time(NULL));
    74 
    75     ret=pthread_create(&pid,NULL,producer,NULL);        //生产者
    76     if (ret!=0)
    77         err_thread(ret,"pthread_create producer error");
    78 
    79     ret=pthread_create(&cid,NULL,consumer,NULL);        //消费者
    80     if (ret!=0)
    81         err_thread(ret,"pthread_create producer error");
    82 
    83     return 0;
    84 }
    条件变量解决生产者消费者问题

    从生产者消费者问题我们可以看出条件变量配合使用的一点好处:

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

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

    信号量

    信号量可以看作是进化版的互斥锁(1 --> N),由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。

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

    同样的,我们以使用步骤开始讲解:

    1,sem_t sem;   规定信号量 sem 不能 < 0。

    2,sem_init 函数  初始化

    3,sem_wait  /  sem_trywait  /  sem_timedwait 函数  ,信号量尝试减一,准备访问共享资源

    4,访问操作共享资源

    5,sem_post 函数  ,访问完成,信号量加一

    6,sem_destroy 函数  销毁信号量

    sem_init 函数
    int sem_init(sem_t *sem, int pshared, unsigned int value);  初始化一个信号量
    参 1:sem 信号量
    参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
    参 3:value 指定信号量初值>=0

    sem_wait 函数
    int sem_wait(sem_t *sem);  给信号量加锁 --

    sem_tryt wait 函数
    int sem_trywait(sem_t *sem);  尝试对信号量加锁 -- (与 sem_wait 的区别类比 lock 和 trylock,即阻塞与非阻塞)

    sem_timedwait 函数
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);  限时尝试对信号量加锁 --
    参 2:abs_timeout 采用的是绝对时间。  注意这里也是timespec,参考上面条件变量,也是绝对时间+纳秒。

    sem_post 函数
    int sem_post(sem_t *sem);  给信号量解锁 ++

    sem_destroy 函数
    int sem_destroy(sem_t *sem);  销毁一个信号量

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<unistd.h>
     4 #include<pthread.h>
     5 #include<semaphore.h>
     6 
     7 #define NUM 5
     8 
     9 int queue[NUM];
    10 sem_t blank_number,product_number;
    11 
    12 void *producer(void *arg) {
    13     int i=0;
    14     while (1) {
    15         //这里是信号量处理生产过程
    16         sem_wait(&blank_number);    //空位数信号量--
    17         queue[i]=rand()%1000+1;    //开始生产
    18         printf("----Produce----%d
    ",queue[i]);
    19         sem_post(&product_number);    //产品数信号量++
    20 
    21         i=(i+1)%NUM;
    22         sleep(rand()%1);
    23     }
    24 }
    25 
    26 void *consumer(void *arg) {
    27     int i=0;
    28     while (1) {
    29         //这里是信号量处理消费过程
    30         sem_wait(&product_number);    //产品数信号量--
    31         //开始消费
    32         printf("---consume--%d
    ",queue[i]);
    33         queue[i]=0;
    34         sem_post(&blank_number);    //空位数信号量++
    35 
    36         i=(i+1)%NUM;
    37         sleep(rand()%3);
    38     }
    39 }
    40 
    41 int main(int argc,char *argv[])
    42 {
    43     pthread_t pid,cid;
    44 
    45     //参二0为线程1为进程,参三可以理解为信号量初始值
    46     sem_init(&blank_number,0,NUM);    //空位数
    47     sem_init(&product_number,0,0);    //产品数
    48 
    49     //创建生产者消费者线程
    50     pthread_create(&pid,NULL,producer,NULL);    
    51     pthread_create(&cid,NULL,consumer,NULL);
    52 
    53     //回收线程
    54     pthread_join(pid,NULL);
    55     pthread_join(cid,NULL);
    56 
    57     //销毁信号量
    58     sem_destroy(&blank_number);
    59     sem_destroy(&product_number);
    60     
    61     return 0;
    62 }
    信号量解决生产者消费者问题
  • 相关阅读:
    leetcode 763 划分字母区间
    leetcode 392 判断子序列
    Leetcode 665 修改一个数成为非递减数组 (Easy)
    leetcode 605 种花问题 贪心算法
    leetcode 452 用最少数量的箭引爆气球 贪心算法
    leetcode 455 分发饼干 贪心算法
    delphi中的 CLX Application
    delphi 之DCOM应用服务器定义函数
    SqlServer 之 sp_executesql系统存储过程的介绍和使用
    delphi 之调用WinSock的API获取本机的机器名称和IP地址
  • 原文地址:https://www.cnblogs.com/clno1/p/12942972.html
Copyright © 2011-2022 走看看