在C/C++的多线程程序中,所有的线程共享相同的地址空间。所以数据段和进程堆的数据,都可以被所有线程读写。在极端情况下,线程的栈空间也可以被共享。只有线程的寄存器上下文算是真正私有的。对于某些变量,我们希望它在不同的线程中具有不同的值,这就是线程私有数据的用途。
对于普通的全局变量,或者通过指针或引用实现的共享,其实在内存中只存在一个备份。而进程私有数据则相当于一种Map
/* * tsd_once.c * * Demonstrate use of pthread_once to initialize something * exactly once within a multithreaded program. * * Note that it is often easier to use a statically initialized * mutex to accomplish the same result. */ #include <pthread.h> #include "errors.h" /* * 线程私有变量的类型. */ typedef struct tsd_tag { pthread_t thread_id; char *string; } tsd_t; pthread_key_t tsd_key; /* 线程私有变量的句柄,所有线程共用一个*/ pthread_once_t key_once = PTHREAD_ONCE_INIT; /* * One-time initialization routine used with the pthread_once * control block. */ void once_routine (void) { int status; printf ("initializing key\n"); status = pthread_key_create (&tsd_key, NULL); if (status != 0) err_abort (status, "Create key"); } /* * Thread start routine that uses pthread_once to dynamically * create a thread-specific data key. */ void *thread_routine (void *arg) { tsd_t *value; int status; status = pthread_once (&key_once, once_routine); if (status != 0) err_abort (status, "Once init"); value = (tsd_t*)malloc (sizeof (tsd_t)); if (value == NULL) errno_abort ("Allocate key value"); /*对象线程私有变量的改写*/ status = pthread_setspecific (tsd_key, value); if (status != 0) err_abort (status, "Set tsd"); printf ("%s set tsd value %p\n", arg, value); value->thread_id = pthread_self (); value->string = (char*)arg; /*对象线程私有变量的读取*/ value = (tsd_t*)pthread_getspecific (tsd_key); printf ("%s starting...\n", value->string); sleep (2); value = (tsd_t*)pthread_getspecific (tsd_key); printf ("%s done...\n", value->string); return NULL; } void main (int argc, char *argv[]) { pthread_t thread1, thread2; int status; status = pthread_create ( &thread1, NULL, thread_routine, "thread 1"); if (status != 0) err_abort (status, "Create thread 1"); status = pthread_create ( &thread2, NULL, thread_routine, "thread 2"); if (status != 0) err_abort (status, "Create thread 2"); pthread_exit (NULL); }
在这个程序中,两个线程以使用相同的线程私有数据句柄去访问线程私有数据(TSD),但他们之间是相互不影响的,这便是TSD所能实现的效果。最近在看Java并发相关的树,发现Java里也有相应的数据结构,具体是在《Java多线程设计模式》的第11章。这里给出示例代码中的比较重要的部分,首先是日志功能类Log。
package tsLog; public class Log { private static final ThreadLocal<TSLog> tsLogCollection = new ThreadLocal<TSLog>(); public static void println(String s) { getTSLog().println(s); } public static void close() { getTSLog().close(); } private static TSLog getTSLog() { TSLog tsLog = (TSLog)tsLogCollection.get(); if(tsLog == null) { tsLog = new TSLog(Thread.currentThread().getName() + "-log.txt"); tsLogCollection.set(tsLog); } return tsLog; } }
其次是用户线程类的实现:
package tsLog; public class ClientThread extends Thread{ public ClientThread(String name) { super(name); } public void run() { System.out.println(getName() + " BEGIN"); for(int i=0; i<10; i++) { Log.println("i = " + i); try { Thread.sleep(100); } catch(InterruptedException e) { } } Log.close(); System.out.println(getName() + " END"); } }
其中Log类的静态代码段会在类加载的时候完成,所以对于整个多线程程序来说,tsLogCollection是唯一且不变的。但是tsLogCollection这个句柄为每个线程维护着一个tsLog示例。所以工作线程在打log的时候不需要把自己的上下文信息传递(即进程id、对应日志文件等)传递给Log。
此外,还有几个小知识点需要记录下:
- 在较新Java版本中,ThreadLocal已经成为泛型类型,实例化最给出线程私有变量的类型
- 由于线程私有变量是专有的,所以不存在同步问题,不用实现访问
发表评论