开篇之前,先考虑这么一个需求:编写一个函数char * strerror(int errno),传入一个错误码,然后返回一个描述该错误的字符串。
很多同学都会这么实现。
#define MAX_STR_LEN 64
static char string[MAX_STR_LEN]={0};
char *strerror(int errno) {
memset(string, 0, sizeof(string));
switch(errno){
case xx:
strlncpy(string, "Error", sizeof(string));
default:
strlncpy(string, "Unknown", sizeof(string));
}
return string;
}
如果是单线程的程序这样子调用没什么问题。但是对于多线程的程序来说就会有问题了。
在一个多线程的程序中,所有的线程共享数据段,静态全局变量string正好在这个数据段里。这样子就有可能导致A线程调用strerror获得的字符串指针指向的内存被另一个线程B改写。
为了避免这种情况,我们需要编写线程安全的代码。下面是线程安全的写法:
#include <stdio.h>
#include <string.h> /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;
#define MAX_ERROR_LEN 256 /* Maximum length of string in per-thread
buffer returned by strerror() */
static void /* Free thread-specific data buffer */
destructor(void *buf)
{
printf("destructor buf = 0x%08x\n", buf);
free(buf);
}
static void /* One-time key creation function */
createKey(void)
{
int s;
/* Allocate a unique thread-specific data key and save the address
of the destructor for thread-specific data buffers */
s = pthread_key_create(&strerrorKey, destructor);
if (s != 0)
errExitEN(s, "pthread_key_create");
printf("createKey addr: 0x%08x, value: 0x%08x\n", &strerrorKey, strerrorKey);
}
char *
strerror(int err)
{
int s;
char *buf;
/* Make first caller allocate key for thread-specific data */
s = pthread_once(&once, createKey);
if (s != 0)
errExitEN(s, "pthread_once");
printf("strerrorKey addr: 0x%08x, value: 0x%08x\n", &strerrorKey, strerrorKey);
buf = pthread_getspecific(strerrorKey);
if (buf == NULL) { /* If first call from this thread, allocate
buffer for thread, and save its location */
buf = malloc(MAX_ERROR_LEN);
if (buf == NULL)
errExit("malloc");
s = pthread_setspecific(strerrorKey, buf);
if (s != 0)
errExitEN(s, "pthread_setspecific");
}
snprintf(buf, "0x%08x\n", buf);
return buf;
}
static void *
threadFunc(void *arg)
{
char *str;
printf("Other thread about to call strerror()\n");
str = strerror(EPERM);
printf("Other thread: str (%p) = %s\n", str, str);
return NULL;
}
int
main(int argc, char *argv[])
{
pthread_t t;
int s;
char *str;
str = strerror(EINVAL);
printf("Main thread has called strerror()\n");
s = pthread_create(&t, NULL, threadFunc, NULL);
if (s != 0)
errExitEN(s, "pthread_create");
s = pthread_join(t, NULL);
if (s != 0)
errExitEN(s, "pthread_join");
s = pthread_create(&t, NULL, threadFunc, NULL);
if (s != 0)
errExitEN(s, "pthread_create");
s = pthread_join(t, NULL);
if (s != 0)
errExitEN(s, "pthread_join");
printf("Main thread: str (%p) = %s\n", str, str);
exit(EXIT_SUCCESS);
}
首先用pthread_once()来调用createKey()来创建一个key。pthread_once()有个特点就是它能保证传入的函数在进程的生命周期里只被执行一次。那么这里即使它被多个线程调用多次,实际上createKey()只会被调用一次,也就是只会创建一个key。所有线程都共享这么一个key。然后每个线程都申请一块内存,并绑定这块内存到这个key,我们叫这个为PTS(Per Thread Storage)。之后各个线程通过这个key可以获取到属于自己的内存。我们用这块内存来保存error字符串,各个线程只访问属于自己的内存,那么就不会出现上面的问题了。
最后每个线程退出的时候,会自动调用desctructor来释放申请的内存。
浙公网安备 33010602011771号