线程参数共享的问题
执行如下三个例子,观察执行结果,分析原因:
#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