您的位置:首页 > 运维架构 > Linux

Linux多线程编程(三)——线程特定数据

2017-05-08 20:34 190 查看
在这一篇章中我们主要来讲讲线程特定数据

线程特定数据,也称为线程私有数据,是存储和查询某个特定线程相关数据的一种机制。引入这个机制的原因是:有时候我们希望每个线程可以访问它自己单独的数据副本,而不需要担心与其他线程的同步访问问题。

我们知道一个进程中的所有线程都可以访问这个进程的整个地址空间。除了使用寄存器以外,一个线程没有办法阻止另一个线程访问它的数据。线程特定数据也不例外。虽然底层的实现部分并不能阻止这种访问能力,但管理线程特定数据的函数可以提高线程间的数据独立性,使得线程不太容易访问到其他线程的线程特定数据。

在分配线程特定数据之前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。使用pthread_key_create创建一个键。

#include <pthread.h>

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));

返回值:若成功,返回0;否则,返回错误编号
创建的键存储在keyp指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址设为空值。

除了创建键以外,pthread_key_create可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经被置为非空值,那么析构函数就会被调用,它唯一的参数就是该数据地址。如果传入的析构函数为空,就表明没有析构函数与这个键关联。当线程调用pthread_exit或者线程执行返回,正常退出时,析构函数就会被调用。同样,线程取消是,只有在最的清理处理程序返回之后,析构函数才会被调用。如果线程调用了exit、_exit、_Exit或abort,或者出现其他非正常的退出时,就不会调用析构函数。

线程退出时,线程特定数据的析构函数将按照操作系统实现中定义的顺序被调用。直到线程所有的键都为空线程特定数据值。

对应所有的线程,我们都可以通过调用pthread_key_delete来取消与线程特定数据值之间的关联关系。

#include <pthread.h>

int pthread_key_delete(pthread_key_t key);

返回值:若成功,返回0;否则,返回错误编号
注意,调用pthread_key_create并不会激活与键关联的析构函数。要释放任何与键关联的线程特定数据值得内存,需要在应用程序中采取额外的步骤。

为了确保分配的键不会由于在初始化阶段的竞争而发生变动,我们可以使用pthread_once。

 

#include <pthread.h>

pthread_once_t initflag = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

返回值:若成功,返回0;否则,返回错误编号
initflag必须是一个非本地变量(如全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。

键一旦创建以后,就可以通过调用pthread_setspecific函数把键和线程特定数据关联起来。可以通过pthread_getspecific函数获得线程特定数据的地址。

#include <pthread.h>

void *pthread_getspecific(pthread_key_t key);

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

返回值:若成功,返回0;否则,返回错误编号
如果没有线程特定数据值与键关联,pthread_getspecific将返回一个空指针,我们可以用这个返回值来确定是否需要调用pthread_setspecific。
下面我们给出一个线程的特定数据使用的例子来帮助我们理解使用它。

这里我们用模板封装了这些API。

#ifndef __PTHREAD_PRIVATE_H__
#define __PTHREAD_PRIVATE_H__

#include <pthread.h>

template<typename T>
class CThreadPrivateData
{
public:
CThreadPrivateData()
{
::pthread_key_create(&m_key, &CThreadPrivateData::destructor);
}

~CThreadPrivateData()
{
::pthread_key_delete(m_key);
}

T &getspecific()
{
T *pSpecificData = static_cast<T *>(::pthread_getspecific(m_key));
if (pSpecificData == NULL)
{
//表明没有线程特定数据值与键关联,需要调用pthread_setspecific
pSpecificData = new T();
::pthread_setspecific(m_key, static_cast<void *>(pSpecificData));
}
return *pSpecificData;
}

private:

static void destructor(void *pData)
{
T *pSpecificData = static_cast<T *>(pData);
if (pSpecificData != NULL)
delete pSpecificData;
}

pthread_key_t m_key;
};

#endif //#ifndef __PTHREAD_PRIVATE_H__
下面就是我们的测试代码了。
#include <string>
#include <stdio.h>
#include <pthread.h>
#include "pthread_private.h"

using namespace std;

class CTest
{
public:
CTest()
{
printf("constructing %p, pthread_id %d\n", this, (int)pthread_self());
}

~CTest()
{
printf("destructing %p, pthread_id %d\n", this, (int)pthread_self());
}

void setMessage(const char *pMessage)
{
m_strMsg = pMessage;
}

string &getMessage(void)
{
return m_strMsg;
}

private:
string m_strMsg;
};

//这里是一个全局对象
CThreadPrivateData<CTest> g_tObj;

void printfInfo(void)
{
printf("pid_t = %d, g_tObj->value() = %p, g_tObj->name = %s\n",
(int)pthread_self(), g_tObj.getspecific(), g_tObj.getspecific().getMessage().c_str());
}

void *pthreadFunc(void *pArg)
{
printf("pthreadFunc runing ...\n");
printfInfo(); //打印未设置之前线程1私有数据的信息
g_tObj.getspecific().setMessage("pthreadId 1");
printfInfo(); //打印设置之后线程1私有数据的信息
printf("pthreadFunc return ...\n");
pthread_exit((void *)1);
}

int main(void)
{
//主线程会创建自己的私有数据,名称为"main pthread"
g_tObj.getspecific().setMessage("main pthread");
printfInfo(); //打印主线程的私有数据信息

pthread_t pthreadId;
void *pThreadRet;

int iRet = pthread_create(&pthreadId, NULL, pthreadFunc, NULL);
if (iRet != 0)
{
printf("pthread_create pthreadId false!\n");
return -1;
}

iRet = pthread_join(pthreadId, &pThreadRet);
if (iRet != 0)
{
printf("pthread_join return false!\n");
return -1;
}
printf("thread exit code %ld\n", (long)static_cast<int *>(pThreadRet));

printfInfo(); //再次打印主线程的私有数据信息
printf("main return 0\n");

return 0;
}
下面是在Linux上的运行结果:
constructing 0x1ed6c20, pthread_id 628426560
pid_t = 628426560, g_tObj->value() = 0x1ed6c20, g_tObj->name = main pthread
pthreadFunc runing ...
constructing 0x7ff01c0008c0, pthread_id 611059456
pid_t = 611059456, g_tObj->value() = 0x7ff01c0008c0, g_tObj->name =
pid_t = 611059456, g_tObj->value() = 0x7ff01c0008c0, g_tObj->name = pthreadId 1
pthreadFunc return ...
destructing 0x7ff01c0008c0, pthread_id 611059456
thread exit code 1
pid_t = 628426560, g_tObj->value() = 0x1ed6c20, g_tObj->name = main pthread
main return 0
从上面的输出结果我们知道:对于全局变量,我们的主线程和线程1都各自拥有自己的私有数据,我们的线程1对全局变量的重置也没有影响主线程的全局变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: