• 程序员造轮子:一个基于posix线程库的互斥类(源码)


    初级代码游戏的专栏介绍与文章目录-CSDN博客

            posix线程库是C++11之前UNIX/LINUX上的标准多线程方案。写起来比较麻烦,所以需要适当包装。

            posix线程库的互斥性能比操作系统的信号量性能高很多,毕竟操作系统的信号量会锁定整个系统,而线程库只锁定本进程。

            posiz线程库挺复杂,不过其实大部分程序需要的功能很简单,不过是互斥和信号而已。互斥用来保护共享数据,信号则用于线程间通知。

    目录

    互斥

    互斥的相关知识点

    信号和等待信号

    信号和等待的相关知识点

    扩展:vector的大坑


    互斥

            最常用的互斥操作只需要锁定和解锁两个方法就可以了:

    1. //线程同步对象
    2. class CPThreadMutex
    3. {
    4. private:
    5. pthread_mutex_t m_mutex;//互斥对象
    6. public:
    7. void init()
    8. {
    9. pthread_mutex_init(&m_mutex, NULL);
    10. }
    11. void destory()
    12. {
    13. pthread_mutex_destroy(&m_mutex);
    14. }
    15. int lock()const { return pthread_mutex_lock((pthread_mutex_t*)&m_mutex); }
    16. int unlock()const { return pthread_mutex_unlock((pthread_mutex_t*)&m_mutex); }
    17. };

            其实相当简单,init和destory还可以写到构造函数和析构函数里面去,看起来更优雅。不过,互斥对象通常都是全局的,需要手动管理,所以太优雅不一定是好事,不慎自动析构就会发生BUG(特别是用vector的时候!!!仔细想想为什么)。

    互斥的相关知识点

            相关知识点很少:

    • pthread_mutex_t 互斥对象,不用管里面有什么。
    • pthread_mutex_init 初始化互斥对象,一般也不需要设置属性,给个互斥对象指针就行了
    • pthread_mutex_destroy 销毁互斥对象
    • pthread_mutex_lock 锁定
    • pthread_mutex_unlock 解锁

    信号和等待信号

            除了锁定之外一般还会用到信号,信号相关函数有点令人困惑,因为一个信号函数必须带一个互斥对象做参数,从内核角度可能这样最合理,但是从外部使用者来看就比较复杂了。

            从这个设计上讲,可以对多个信号对象的操作使用同一个互斥对象,但是什么场景需要这样做?每个信号对象各自在内部保证自己的正确性不够吗?

            增加了信号和等待的代码如下:

    1. //线程同步对象
    2. class CPThreadMutex
    3. {
    4. private:
    5. pthread_mutex_t m_mutex;//互斥对象
    6. pthread_cond_t m_cond;//条件变量
    7. public:
    8. void init()
    9. {
    10. pthread_mutex_init(&m_mutex, NULL);
    11. pthread_cond_init(&m_cond, NULL);
    12. }
    13. void destory()
    14. {
    15. pthread_cond_destroy(&m_cond);
    16. pthread_mutex_destroy(&m_mutex);
    17. }
    18. int lock()const { return pthread_mutex_lock((pthread_mutex_t*)&m_mutex); }
    19. int unlock()const { return pthread_mutex_unlock((pthread_mutex_t*)&m_mutex); }
    20. int wait()const { return pthread_cond_wait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex); }
    21. int reltimedwait()const
    22. {
    23. timespec to;
    24. to.tv_sec = time(NULL) + 1;
    25. to.tv_nsec = 0;
    26. int ret;
    27. ret = pthread_cond_timedwait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex, &to);
    28. if (0 == ret)return true;
    29. else if (ETIMEDOUT == ret)return false;
    30. else throw ret;
    31. return false;
    32. }
    33. int signal()const { return pthread_cond_signal((pthread_cond_t*)&m_cond); }
    34. };

            reltimedwait每次等待1秒钟,你可以很容易地把时间做成参数。

            相关接口和互斥对象差不多,只不过pthread_cond_wait和pthread_cond_timedwait额外需要一个互斥对象做参数,在这个类里面使用了共同的互斥对象,整个类的所有操作都被单一互斥对象保护。

            一般操作流程是一个线程等待信号而另一个线程会在适当的时候发出信号。

    信号和等待的相关知识点

            相关知识点:

    • pthread_cond_t 条件变量,不用关心具体结构
    • pthread_cond_init 初始化条件变量,一般不用设置属性
    • pthread_cond_destroy 销毁条件变量
    • pthread_cond_signal 发出信号,以条件变量为参数
    • pthread_cond_wait 等待信号,除了条件变量参数,还需要互斥对象参数来进行互斥
    • pthread_cond_timedwait 等待信号,增加了时间参数,这个时间参数是结束时间点,而不是一般以为的时长

    扩展:vector的大坑

            前面说了析构函数和vector凑在一起会有大坑,这是怎么回事呢?当对象里面的东西是系统资源如文件、信号量等的时候,打开关闭这种动作是不可以自动进行的,而vector在动态增长时会复制对象然后删除原来的,从而导致资源被错误释放。由此引起的BUG很令人困惑,要折腾很久才能找到原因。

            而set、map不会有这个问题,因为节点是不会随着数据增加而被移动的。vector可以用reserve来避免移动数据,但是,很多时候无法预知最大数据量是多少,所以没有解决办法。

    (这里是结束)

  • 相关阅读:
    霸道的 AliPaladin64.sys
    SpringCloud - Spring Cloud Alibaba 之 Seata分布式事务服务;TCC事务模式机制(二十三)
    Strimzi Kafka Bridge(桥接)实战之三:自制sdk(golang版本)
    MetaGPT部分源码解读
    framebuffer驱动
    nodejs多版本管理
    AI绘画本地部署Stable Diffusion web UI
    Ax=y,Ax=0以及非线性方程组的最小二乘解
    2022年全球服务器AI芯片市场竞争与发展前景研究
    汇编语言中断及外部设备操作篇--06
  • 原文地址:https://blog.csdn.net/2301_77171572/article/details/134263165