• Linux系统编程3_条件变量与互斥锁


    例子:

    生产者,消费者问题;
    消费者先进入临界区,条件变量未满足条件,阻塞等待;
    生产者无法进入临界区,从而无法修改条件变量,也就产生死锁;

    解决方法:
    如果遇到条件变量未满足条件,消费者先释放锁,进入阻塞,等待条件变量得到满足;
    然后生产者可以进入临界区修改条件变量,修改后通知消费者进入临界区,生产者释放锁
    消费者接收到通知申请锁,得到锁后,发现条件变量得到满足,开始消费,消费完毕释放锁

    流程结束,完美解决生产者消费者同步的问题;

    这就是条件变量+互斥锁的应用;



    条件变量
    条件变量和互斥锁一样,都有静态动态两种创建方式;
    静态方式使用PTHREAD_COND_INITIALIZER常量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER
    动态方式调用函数int pthread_cond_init
    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

    条件变量的属性由参数attr指定,如果参数attr为NULL,那么就使用默认的属性设置。
    尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
    多线程不能同时初始化一个条件变量,因为这是原子操作。

    如果函数调用成功,则返回0,并将新创建的条件变量的ID放在参数cond中。

    删除条件变量
    int pthread_cond_destroy(pthread_cond_t *cond);
    调用destroy函数解除条件变量并不会释放存储条件变量的内存空间。

    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abtime);

    等待有两种方式:条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait()
    其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待
    其中abstime以与系统调用time相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

    无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()或pthread_cond_timedwait()(下同)的竞争条件(Race Condition)。
    mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者自适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),
    且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),
    而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。
    在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
    阻塞时处于解锁状态。这样不会造成死锁;


    int pthread_cond_signal(pthread_cond_t *cond);
    pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,
    继续执行,如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。

    int pthread_cond_broadcast(pthread_cond_t *cond);
    可以唤醒所有wait该cond的线程;

    条件变量是利用线程间共享的全局变量进行同步的一种机制;
    主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
    为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。



    互斥锁的作用
    保护共享数据: 在并发机制的情况下,有时候会有多个线程同时访问同一片数据,为了保护数据操作的准确性就需要通过加锁来进行保护。
    保持操作互斥: 可能一个程序会有多个操作,但是同一个时间只能有一个操作被执行,例如a/b两个操作,如果a被执行,b就不能被执行,同理b被执行,a就不能执行

    pthread_mutex_t lock; /* 互斥锁定义 */
    pthread_mutex_init(&lock, NULL); /* 动态初始化, 成功返回0,失败返回非0 */
    pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化 */
    pthread_mutex_lock(&lock); /* 阻塞的锁定互斥锁 */
    pthread_mutex_trylock(&thread_mutex);/* 非阻塞的锁定互斥锁,成功获得互斥锁返回0,如果未能获得互斥锁,立即返回一个错误码 */
    pthread_mutex_unlock(&lock); /* 解锁互斥锁 */
    pthread_mutex_destroy(&lock) /* 销毁互斥锁 */



    多线程
    pthread_t 用于声明线程ID unsigned long int
    4,(不同环境大小不一)x86_64=8


    多线程相关函数介绍:

    线程创建//pthread_create是UNIX环境创建线程函数
    原型:
    int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
    若成功则返回0,否则返回出错编号;
    第一个参数为指向线程标识符的指针。
    第二个参数用来设置线程属性。
    第三个参数是线程运行函数的地址。
    最后一个参数是运行函数的参数。

    在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。


    函数pthread_join用来等待一个线程的结束。
    extern int pthread_join __P (pthread_t __th, void **__thread_return);
    第一个参数为被等待的线程标识符
    第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。
    pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。
    如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
    返回值 : 0代表成功。 失败,返回的则是错误号。

    另外需要说明的是,一个线程不能被多个线程等待,
    也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返回ESRCH 错误。

    在Linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,
    但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源,
    详细资料查看Threads attributes。

    线程属性,使用pthread_attr_t类型表示;需要对此结构体进行初始化;
    初始化后使用,使用后还要进行去除初始化!
    int pthread_attr_init(pthread_attr_t *attr);
    int pthread_attr_destroy(pthread_attr_t *attr);
    若成功返回0,若失败返回-1。

    pthread_create 创建线程时,若不指定分配堆栈大小,系统会分配默认值
    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    attr 是线程属性变量;stacksize 则是设置的堆栈大小。 返回值0,-1分别表示成功与失败。


    线程私有变量://一键多值,表面上是全局变量,实际上是线程私有的数据空间;
    int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
    第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。

    int pthread_setspecific(pthread_key_t key, const void *value);
    当线程中需要存储特殊值的时候,可以调用 pthread_setspcific() 。
    该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。

    如果需要取出所存储的值,调用pthread_getspecific()。
    该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。
    void *pthread_getspecific(pthread_key_t key);

    pthread_cond_signal函数的作用是
    发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行。

    如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。



    参考资料



  • 相关阅读:
    104每日博客
    924每日博客
    921每日博客
    928每日博客
    929每日博客
    930每日博客
    927每日博客
    大志非才不就,大才非学不成—我的博文资源汇总
    vue 项目搭建
    Vant 使用记录
  • 原文地址:https://www.cnblogs.com/grooovvve/p/12885928.html
Copyright © 2020-2023  润新知