线程参数共享的问题

执行如下三个例子,观察执行结果,分析原因:

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

void *start_routine(void * param)
{
    int index = *(int *)param;
    printf("%s:%d\n",__func__,index);
    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
   int i = 0;
   void * ret = NULL;
   pthread_t ids[THREADS_NR] = {0};
   for(i = 0;i < THREADS_NR;i++)
   {
       pthread_create(ids+i,NULL,start_routine,&i);
   }
   for(i = 0; i < THREADS_NR;i++)
   {
       pthread_join(ids[i],&ret);
   }
   return ;
}
int main()
{
    create_test_threads();
    return 0;
}

执行结果:

[root@centos-7 workspace]# ./pthread
start_routine:2
start_routine:2
start_routine:3
start_routine:3
start_routine:3
start_routine:3
start_routine:3
start_routine:3
start_routine:4
start_routine:3
[root@centos-7 workspace]#
[root@centos-7 workspace]# ./pthread
start_routine:4
start_routine:9
start_routine:5
start_routine:5
start_routine:9
start_routine:5
start_routine:6
start_routine:7
start_routine:7
start_routine:9
[root@centos-7 workspace]# ./pthread
start_routine:2
start_routine:6
start_routine:1
start_routine:1
start_routine:2
start_routine:4
start_routine:2
start_routine:2
start_routine:5
start_routine:3

分析执行结果,由于线程间无法确定执行的先后顺序(下一个例子更好的说明),i的值不断变化,在执行phtread_join时又将i的值从0开始,因此打印出来的值是随机的。

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

void *start_routine(void * param)
{
    int index = *(int *)param;
    printf("%s:%d\n",__func__,index);
    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
   int i = 0;
   void * ret = NULL;
   int array[THREADS_NR]={0,1,2,3,4,5,6,7,8,9};
   pthread_t ids[THREADS_NR] = {0};
   for(i = 0;i < THREADS_NR;i++)
   {
       pthread_create(ids+i,NULL,start_routine,&array[i]);
   }

   for(i = 0;i < THREADS_NR;i++)
   {
       pthread_join(ids[i],&ret);
   }

   return ;
}
int main()
{
    create_test_threads();
    return 0;
}

执行结果:

[root@centos-7 workspace]# ./pthread
start_routine:2
start_routine:1
start_routine:0
start_routine:3
start_routine:7
start_routine:8
start_routine:6
start_routine:4
start_routine:5
start_routine:9
[root@centos-7 workspace]#
[root@centos-7 workspace]# ./pthread
start_routine:0
start_routine:3
start_routine:6
start_routine:7
start_routine:1
start_routine:8
start_routine:4
start_routine:5
start_routine:2
start_routine:9
[root@centos-7 workspace]# ./pthread
start_routine:2
start_routine:4
start_routine:1
start_routine:0
start_routine:3
start_routine:7
start_routine:5
start_routine:9
start_routine:6
start_routine:8

分析执行结果,这里我们每个进程赋了不同的值,更能说明线程间的执行顺序无法确定这一情况。

#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <unistd.h>

void *start_routine(void * param)
{
    int index = *(int *)param;
    printf("%s:%d\n",__func__,index);
    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
   int i = 0;
   void * ret = NULL;
   int array[THREADS_NR]={0,1,2,3,4,5,6,7,8,9};
   pthread_t ids[THREADS_NR] = {0};
   for(i = 0;i < THREADS_NR;i++)
   {
       pthread_create(ids+i,NULL,start_routine,&i);
   }

   sleep(10);
   for(i = 0;i < THREADS_NR;i++)
   {
       pthread_join(ids[i],&ret);
   }

   return ;
}
int main()
{
    create_test_threads();
    return 0;
}

执行结果:

[root@centos-7 workspace]# ./pthread
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
start_routine:10
[root@centos-7 workspace]# ./pthread
start_routine:4
start_routine:5
start_routine:5
start_routine:5
start_routine:7
start_routine:5
start_routine:6
start_routine:8
start_routine:9
start_routine:10
[root@centos-7 workspace]# ./pthread
start_routine:7
start_routine:7
start_routine:7
start_routine:6
start_routine:8
start_routine:9
start_routine:9
start_routine:9
start_routine:9
start_routine:10
[root@centos-7 workspace]#

start_routine函数内打印出来的值,最少会有一个10,最多全部为10,我们添加sleep的作用是避免下一个循环修改i的值。

TLS--线程局部存储

概念:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来。

进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。

如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。

方法一:
/* CELEBP34 */
#ifndef _OPEN_THREADS
#define _OPEN_THREADS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

#define threads 3
#define BUFFSZ  48
pthread_key_t   key;

void *threadfunc(void *parm)
{
 int        status;
 void      *value;
 int        threadnum;
 int       *tnum;
 void      *getvalue;
 char       Buffer[BUFFSZ];

 tnum = parm;
 threadnum = *tnum;

 printf("Thread %d executing\n", threadnum);

 if (!(value = malloc(sizeof(Buffer))))
     printf("Thread %d could not allocate storage, errno = %d\n",
                                                  threadnum, errno);
 status = pthread_setspecific(key, (void *) value);
 if ( status <  0) {
    printf("pthread_setspecific failed, thread %d, errno %d",
                                                  threadnum, errno);
    pthread_exit((void *)12);
 }
 printf("Thread %d setspecific value: %p\n", threadnum, value);

 getvalue = 0;
 getvalue = pthread_getspecific(key);

 if (getvalue != value) {
   printf("getvalue not valid, getvalue=%d", *(int*)getvalue);
   pthread_exit((void *)68);
 }

 pthread_exit((void *)0);
}

void  destr_fn(void *parm)
{

   printf("Destructor function invoked\n");
   free(parm);
}


int main() {
 int          status;
 int          i;
 int          threadparm[threads];
 pthread_t    threadid[threads];
 int          thread_stat[threads];

 if ((status = pthread_key_create(&key, destr_fn )) < 0) {
    printf("pthread_key_create failed, errno=%d", errno);
    exit(1);
 }

 /* create 3 threads, pass each its number */
 for (i=0; i<threads; i++) {
    threadparm[i] = i+1;
    status = pthread_create( &threadid[i],
                             NULL,
                             threadfunc,
                             (void *)&threadparm[i]);
    if ( status <  0) {
       printf("pthread_create failed, errno=%d", errno);
       exit(2);
    }
  }

 for ( i=0; i<threads; i++) {
    status = pthread_join( threadid[i], (void *)&thread_stat[i]);
    if ( status <  0) {
       printf("pthread_join failed, thread %d, errno=%d\n", i+1, errno);
    }

    if (thread_stat[i] != 0)   {
        printf("bad thread status, thread %d, status=%d\n", i+1,
                                                   thread_stat[i]);
      }
  }

 exit(0);
}

例子来自ibm knowledge center,原文中存在错误,代码居然编不过,这不是骗人呢吗,上面的例子是修正后的版本,如果万一有朋友发现存在问题,欢迎留言告知。

上面的例子中每个线程内部申请了内存,然后将内存地址存入key,程序结束后销毁程序会释放所申请的内存。
使用valgrind执行程序结果:

==2389== Memcheck, a memory error detector
==2389== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2389== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2389== Command: ./tld
==2389==
Thread 1 executing
Thread 1 setspecific value: 0x5c4d720
Destructor function invoked
Thread 2 executing
Thread 2 setspecific value: 0x5c4e0e0
Destructor function invoked
Thread 3 executing
Thread 3 setspecific value: 0x5c4e150
Destructor function invoked
==2389==
==2389== HEAP SUMMARY:
==2389==     in use at exit: 0 bytes in 0 blocks
==2389==   total heap usage: 12 allocs, 12 frees, 3,678 bytes allocated
==2389==
==2389== All heap blocks were freed -- no leaks are possible
==2389==
==2389== For counts of detected and suppressed errors, rerun with: -v
==2389== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

stackoverflow上一个不错的例子:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t tidKey;

static void destructor(void *buf) {
    unsigned long *_tid = buf;
    printf("destroy, tid: %lu\n", *_tid);
    free(buf);
}

static void createKey(void) {
    int s = pthread_key_create(&tidKey, destructor);
    if(s != 0) {
        printf("failed to create key\n");
        exit(-1);
    }
}

void *store_tid() {
    int s;
    unsigned long *buf;

    // create key
    s = pthread_once(&once, createKey);
    if(s != 0) {
        printf("failed to create key\n");
        exit(-1);
    }

    buf = pthread_getspecific(tidKey);
    if(buf == NULL) { // thread call this function for the first time,
        buf = malloc(sizeof(unsigned long));
        if(buf == NULL) {
            printf("failed to allocate memory, %s\n", strerror(errno));
            exit(-1);
        }
        // register buffer to specified key & current thread,
        s = pthread_setspecific(tidKey, buf);
        if(s != 0) {
            printf("failed to setspecific\n");
            exit(-1);
        }
    }

    // store tid to buffer,
    *buf = (unsigned long)pthread_self();
    printf("set tid to: %lu\n", *buf);

    return buf;
}

void tsd_test() {
    unsigned long *tidp_a = store_tid();
    printf("tid - before call another thread: %lu\n", *tidp_a);

    int s;
    pthread_t t2;
    s = pthread_create(&t2, NULL, &store_tid, NULL);
    if(s != 0) {
        printf("failed to create thread\n");
        exit(-1);
    }

    s = pthread_join(t2, NULL);
    if(s != 0) {
        printf("failed to join thread\n");
        exit(-1);
    }

    tidp_a = store_tid();

    printf("tid - after call another thread: %lu\n", *tidp_a);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    tsd_test();
    return 0;
}

执行结果:

[root workspace]#./pthread_key_creat
set tid to: 140523198994240
tid - before call another thread: 140523198994240
set tid to: 140523190482688
destroy, tid: 140523190482688
set tid to: 140523198994240
tid - after call another thread: 140523198994240
destroy, tid: 140523198994240
方法二 __thread 关键字
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

__thread int var=5;

void* worker1(void* arg);
void* worker2(void* arg);

int main()
{
    pthread_t pid1,pid2;

    pthread_create(&pid1,NULL,worker1,NULL);
    pthread_create(&pid2,NULL,worker2,NULL);
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);

    return 0;
}

void* worker1(void* arg){
    ++var;
    printf("%d\n",var);
}

void* worker2(void* arg){
    sleep(1);
    ++var;
    printf("%d\n",var);
}

程序输出:

6
6