zoukankan      html  css  js  c++  java
  • 关键字:__thread & pthread_key_t

      在说__thread之前,先来看看pthread_ket_t吧。

      参考:http://blog.csdn.net/lmh12506/article/details/8452700

      上面的博文说的比较通俗易懂。线程私有数据可以理解为线程内的全局变量。在线程内可以被所有函数访问,但是不能被其他线程的函数访问。

      这里博主直接去找pthread.h头文件中的API,发现写的还是很详细的。

    /* Functions for handling thread-specific data.  */    //用于处理线程特定数据的函数。
    
    /* Create a key value identifying a location in the thread-specific      //identifying 识别
       data area.  Each thread maintains a distinct thread-specific data      //maintains 维护  distinct 不同的
       area.  DESTR_FUNCTION, if non-NULL, is called with the value
       associated to that key when the key is destroyed.                //associated to 关联 即__destr_function如果不是null,则被关联到key销毁的时刻
       DESTR_FUNCTION is not called if the value associated is NULL when
       the key is destroyed.  */
    extern int pthread_key_create (pthread_key_t *__key,
                                   void (*__destr_function) (void *))
         __THROW __nonnull ((1));
    
    /* Destroy KEY.  */
    extern int pthread_key_delete (pthread_key_t __key) __THROW;
    
    /* Return current value of the thread-specific data slot identified by KEY.  */    //slot 槽 这个理解好,把线程局部存储设施比喻为槽。
    extern void *pthread_getspecific (pthread_key_t __key) __THROW;              //注意这个返回值,使用时记得把void* 转换成指定的类型
    
    /* Store POINTER in the thread-specific data slot identified by KEY. */        //往槽里存储数据
    extern int pthread_setspecific (pthread_key_t __key,
                                    const void *__pointer) __THROW ;

       

      贴一段博主自己在电脑上的测试:

    #include <pthread.h>
    #include <iostream>
    #include <vector>
    #include <string.h>
    #include <stdio.h>
    #include <unistd.h>
    using namespace std;
    
    static pthread_key_t pkt;
    //FILE* log_handle;
    
    void destroy(void *arg)
    {
    //      sleep(10);
            fclose((FILE*)arg);    // <1>
    }
    
    void write_log(const char* log)
    {
            FILE* log_handle = reinterpret_cast<FILE*> (pthread_getspecific(pkt));  //从槽中拿值
            fprintf(log_handle, "%s
    ", log);
    }
    
    void* work(void *arg)
    {
            FILE *log_handle;
            char file_name[128] = "";
    
            sprintf(file_name, "/home/ttsj/log/Thread%d.log", static_cast<int>(pthread_self()));
    
            cout << file_name << endl;
            log_handle = fopen(file_name, "w");
    
            pthread_setspecific(pkt, reinterpret_cast<void*> (log_handle));  //向线程私有存储槽中存入值
    
            write_log("Thread starting.");
    
            //work here...
    }
    
    
    int main()
    {
            vector<pthread_t> savepid;
    
            pthread_key_create(&pkt, destroy);  //<2>
    
            int i;
            for(i = 0; i < 2; ++i)
            {
                    savepid.push_back(i);
            }
    
            for(i = 0; i < 2; ++i)
            {
                    pthread_create(&savepid[i], NULL, work, NULL);  //拉5个线程
            }
    
            for(i = 0; i < 2; ++i)
            {
                    cout << "join" << i << endl;
                    pthread_join(savepid[i], NULL);
            }
    
            pthread_key_delete(pkt);
            sleep(10);
    
            return 0;
    }

      其实使用没什么难的,但博主却折腾了两个小时,知道为什么吗?请仔细看看<1>和<2>.刚开始就是不知道如何给销毁函数destroy传递参数,而不传递参数,又如何关闭流呢?经过博主两个小时的验证,才搞明白了:看<1>处,得知这个参数根本不用传,系统自动给你传了,而传递的值是什么呢?哈哈,就是线程私有数据!看下面,在程序运行时,destroy成功通过参数关闭了文件流,说明系统确实把线程私有数据(打开的文件流)传递给了pthread_create的第二个参数,而这一切都不用我们自己动手了!

      

      对于pthread_key_t的使用,最好使用RAII:

    //博主参照muduo写的
    #include <pthread.h> #include <vector> #include <algorithm> #include <iostream> #include <boost/checked_delete.hpp>
    template <typename T> class ThreadLocal { public: typedef ThreadLocal<T>* pThreadLocal;  //指向模板的指针,应该用typedef定义一个别名来使用 private: pthread_key_t pkey; private: static void destroy(void *x) { T *obj = static_cast <T*> (x); boost::checked_delete(obj);  //保证可以调用对象的析构函数 } public: ThreadLocal()  //这些操作都放进构造和析构,自动执行 { pthread_key_create(&pkey, destroy); } ~ThreadLocal() { pthread_key_delete(pkey); } T& value()  //始终保证槽内只有一个T类型对象 { T* pThreadData = static_cast <T*> (pthread_getspecific(pkey)); if(pThreadData == NULL) { T* newData = new T(); pthread_setspecific(pkey, newData); pThreadData = newData; } return *pThreadData; } }; class base { private: int count = 4; public: void show() { count++; std::cout << count << std::endl; } }; void* work(void* args) { ThreadLocal<base>::pThreadLocal p = static_cast<ThreadLocal<base>::pThreadLocal>(args);  //p指向线程局部存储设施 base &pb = p->value();  //如果槽内没有对象,则构造一个对象。始终保证槽内存在一个对象。 pb.show();        //使用槽内的对象 } int main() { std::vector<pthread_t> vec(2); ThreadLocal<base>::pThreadLocal p = new ThreadLocal<base>;  //先构造一个ThreadLocal对象,此对象封装了线程局部存储设施。 for(int i = 0; i < 2; ++i) pthread_create(&vec[i], NULL, work, static_cast<void*>(p));  //拉两个线程,并把上述对象传递过去 for(int i = 0; i < 2; ++i) pthread_join(vec[i], NULL); delete p; }

    输出结果为 5和5 。可见两个线程未互相影响,base对象是存放在各自线程局部存储槽内的 

      再来说说__thread:

      陈硕在书中提了这个关键字,说是比pthread_key_t效率高,具体怎么高,博主也不会测。

      根据书中的说法:GCC内置的线程局部存储设施。只能用于修饰POD类型,不能修饰class类型(即不能修饰一个对象),因为无法自动调用构造函数和析构函数(有道理,毕竟是线程局部存储区域,c++的魔抓可能伸不到这里来)。__thread可以修饰全局变量,函数内的静态变量,但是不能用于修饰函数的局部变量(上面的pthread_key_t可以,用来存储一个栈上的流对象)或者class的普通成员变量。另外,__thread变量的初始化只能用编译期常量(new的话就别想了).

     __thread string str;   //error,不能调用对象的构造函数

       __thread string *pStr = new string;  //error,初始化必须用编译期常量

      __thread变量是每个线程有一份独立的实体,各个线程的变量值互不干扰。还有个用途:用来修饰“值可能会变,带有全局性,但是又不值得用全局锁保护”的变量。如果一个值,想在线程内被所有函数访问,但又不想被其他线程影响,可以试试__thread.

      总之,用法不难,最重要的是要知道在什么时候用。这些都得靠经验之积累。

    拓展:

     POD类型:

      POD 类型(纯旧数据):C++ 中的此类非正式数据类型类别是指作为标量(参见基础类型部分)的类型或 POD 类。 POD 类没有不是 POD 的静态数据成员,没有用户定义的构造函数、用户定义的析构函数或用户定义的赋值运算符。 此外,POD 类无虚函数、基类、私有的或受保护的非静态数据成员。 POD 类型通常用于外部数据交换,例如与用 C 语言编写的模块(仅具有 POD 类型)进行的数据交换。

     pthread_join等待多个线程问题:

     for(int i =0; i < 5; ++i)
    
      pthread_join(pid[i],NULL)
    实际上主线程在pthread_join(1,NULL);这里就挂起了,在等待1号线程结束后再等待2号线程。在等待玩1后,2345线程可能已经结束了,但主线程依然可以回收。


    checked_delete提升安全性
    依然先贴代码:
    //curr.h
    class prev;
    class curr
    {
    public:
        void Delete(prev* p) {
            delete p;    //此处删除,无法调用prev的析构函数,即prev没有被真正销毁。因为curr不知道prev的详细定义。编译器会给出警告,但不会报错。造成内存泄露
        }
    };
    // prev.h
    class prev
    {
    public:
        ~prev() {
            cout << "delete prev" << endl;    
    };
    
    int main()
    {
        curr obj;
        obj.Delete(new obj);  
    }

    这里应该可以使用boost的checked_delete(T *p) 进行检查。对不完全类的删除,都应该用这个去删除。当然最好直接用智能指针。贴一下cheched_delete的boost源码:

    template<class T> inline void checked_delete(T * x)
    {
        // intentionally complex - simplification causes regressions
        typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];  //如果是不完全类型,则[]内是-1,[-1]是不允许的
        (void) sizeof(type_must_be_complete);
        delete x;
    }
    
    template<class T> inline void checked_array_delete(T * x)
    {
        typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
        (void) sizeof(type_must_be_complete);
        delete [] x;
    }
    
    template<class T> struct checked_deleter
    {
        typedef void result_type;
        typedef T * argument_type;
    
        void operator()(T * x) const
        {
            // boost:: disables ADL
            boost::checked_delete(x);
        }
    };
    
    template<class T> struct checked_array_deleter
    {
        typedef void result_type;
        typedef T * argument_type;
    
        void operator()(T * x) const
        {
            boost::checked_array_delete(x);
        }
    };

    用法很简单。当然,你得首先安装一下boost库:
    #include <boost/checked_delete.hpp>
    
    void Delete(prev *p)
    {
        boost::checked_delete(p);
    }
  • 相关阅读:
    Day5下午解题报告1
    Linux 命令整理
    [置顶] 正则表达式应用:匹配email地址
    IE浏览器下web调试工具之--IE WebDeveloper介绍
    ORACLE客户端乱码
    冒泡排序
    【C++】第二章:Hello World!
    hdu4710
    (SQL SERVER) (ORACLE) (ACCESS)(POSTGRE SQL)四种数据库操作C#代码
    How to calculate the undo_retention time
  • 原文地址:https://www.cnblogs.com/-ttsj/p/6504988.html
Copyright © 2011-2022 走看看