• c/c++ 线程私有数据


           我们知道,不管是在单线程还是在多线程中,全局变量都可以在各线程中共享数据。但当我们需要在各线程中保存各自线程的数据时,该如何处理呢?是在各线程中各自定义数据呢,还是有其他办法呢?pthread 线程库就有这样一种数据结构,  相同的数据名称在不同的线程中各自维护着各自的数据,称为线程私有数据(Thread Specific Data TSD)或称为 线程本地存储 TSS(Thread-Specific Storage), TLS(Thread Local Storage)。而 pthread 线程库一共提供了 4 个接口用来创建、删除、设置、获取 这个线程私有数据,分别为:

     #include  

    int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); //第 2 个参数是一个函数指针,用来在线程退出时将key值作为参数进行调用。(这个参数暂时没用过)

     int pthread_key_delete(pthread_key_t key);

     int pthread_setspecific(pthread_key_t key, const void *value);

     void *pthread_getspecific(pthread_key_t key);

    类型 pthread_key_t 实际定义为:

    /* Keys for thread-specific data */

    typedef unsigned int pthread_key_t;

    我们可以用一个类进行封装,如下:

    1. /************************************
    2. *
    3. * filename: ThreadSpecific.h
    4. *
    5. ************************************/
    6. #ifndef __THREAD_SPECIFIC_H__
    7. #define __THREAD_SPECIFIC_H__
    8. #include
    9. class CThreadSpecific
    10. {
    11. public:
    12. CThreadSpecific();
    13. ~CThreadSpecific();
    14. // 设置私有数据
    15. bool setValue(void const *value);
    16. // 获取私有数据
    17. bool getValue(void **value);
    18. private:
    19. bool mValid; //数据是否可用,即调用 pthread_key_create 过了
    20. pthread_key_t mKey;
    21. };
    22. #endif
    1. /************************************
    2. *
    3. * filename: ThreadSpecific.cpp
    4. *
    5. ************************************/
    6. #include "ThreadSpecific.h"
    7. CThreadSpecific::CThreadSpecific(): mValid(false), mKey(0)
    8. {
    9. mValid = (0 == pthread_key_create(&mKey, NULL));
    10. }
    11. CThreadSpecific::~CThreadSpecific()
    12. {
    13. if(mValid)
    14. {
    15. pthread_key_delete(mKey);
    16. }
    17. }
    18. bool CThreadSpecific::setValue(void const *value)
    19. {
    20. if(mValid)
    21. {
    22. return 0 == pthread_setspecific(mKey, value);
    23. }
    24. return false;
    25. }
    26. bool CThreadSpecific::getValue(void **value)
    27. {
    28. if(mValid)
    29. {
    30. *value = pthread_getspecific(mKey);
    31. return true;
    32. }
    33. return false;
    34. }

            那线程私有数据有什么用呢?毫无疑问是用在多线程情况下,比如场景:一台嵌入式设备可以通过局域网进行登录,登录后可以进行配置操作或其他操作,而同时它是允许多个用户操作的,这时多个用户操作其实就是多个线程在操作,那么各个用户(线程)获取到的返回码肯定是要不一样的。假如用一个全局变量会发生什么样的情况呢?可能是:前一个用户操作错误时,紧接着一个用户操作没有错误,而取到是前一个用户操作的错误码,这样是不是混乱了呢?所以这种情况下,各个线程操作的结果应该是各个线程进行私有设置,这样各个线程获取到的错误码才是正确的。

            那实际使用时可以再添加一个管理错误码的类,实现为一个单例,然后提供设置和获取错误码的接口,类似如下:

    1. /****************************
    2. * filename: ErrorManager.h
    3. ***************************/
    4. #ifndef __ERROR_MANAGER_H__
    5. #define __ERROR_MANAGER_H__
    6. #include "ThreadSpecific.h"
    7. class CErrorManager
    8. {
    9. private:
    10. CErrorManager()
    11. {}
    12. ~CErrorManager()
    13. {}
    14. CErrorManager(const CErrorManager&) = delete;
    15. CErrorManager& operator=(const CErrorManager&) = delete;
    16. public:
    17. static CErrorManager *instance();
    18. bool setLastError(int errno);
    19. int getLastError();
    20. };
    21. #endif
    1. /*******************************
    2. * filename: ErrorManager.cpp
    3. ******************************/
    4. #include
    5. #include "ErrorManager.h"
    6. static CThreadSpecific sTSSLastError;
    7. CErrorManager *CErrorManager::instance()
    8. {
    9. static CErrorManager sInstance;
    10. return &sInstance;
    11. }
    12. bool CErrorManager::setLastError(int errno)
    13. {
    14. return sTSSLastError.setValue(reinterpret_cast<void const*>(errno));
    15. }
    16. int CErrorManager::getLastError()
    17. {
    18. void *value = NULL;
    19. sTSSLastError.getValue(&value);
    20. return ((int)reinterpret_cast<intptr_t>(value));
    21. }

    main 函数如下,创建两个线程,用简单的 sleep() 方式先后启动线程函数,同时交叉地设置了各自的线程私有数据,然后再获取。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "ErrorManager.h"
    7. void *threadProc(void *arg)
    8. {
    9. int errNo = *(int *)arg;
    10. printf("tid[%ld] setLastError %d to threadspecific!\n", syscall(SYS_gettid), errNo);
    11. CErrorManager::instance()->setLastError(errNo);
    12. sleep(3);
    13. int value = CErrorManager::instance()->getLastError();
    14. printf("tid[%ld] getLastError %d from threadspecific!\n", syscall(SYS_gettid), value);
    15. return nullptr;
    16. }
    17. int main()
    18. {
    19. pthread_t tid1, tid2;
    20. int value = 200;
    21. pthread_create(&tid1, nullptr, threadProc, (void *)&value);
    22. sleep(1);
    23. value = 500;
    24. pthread_create(&tid2, nullptr, threadProc, (void *)&value);
    25. pthread_join(tid1, nullptr);
    26. pthread_join(tid2, nullptr);
    27. return 0;
    28. }

    其结果为:

    各个线程获取的都是各自设置的数据。 

  • 相关阅读:
    使用selenium驱动浏览器
    规则和人
    硬核,腾讯内部整理的面向开发人员代码安全指南,适合所有程序员
    AI+Science系列(二):国内首个基于AI框架的CFD工具组件!赛桨v1.0 Beta API介绍以及典型案例分享!
    windows c++开发
    开发区块链DApp应用,引领数字经济新潮流
    前端学习路线参考
    Apache Ranger安装部署
    漏洞复现----42、Spring Cloud Gateway Actuator API SpEL表达式注入命令执行(CVE-2022-22947)
    缓存更新策略(旁路更新策略)
  • 原文地址:https://blog.csdn.net/tianyexing2008/article/details/126324742