上一篇文章总结了POSIX线程模型,POSIX相关函数以及使用线程实现的回射客户/服务器。接下来学习线程的属性以及线程特定数据的相关内容。
1.线程属性
(1)pthread_create函数创建线程的时候,可以指定一个线程的属性,我们前面使用该函数的时候由于不关心该属性即为默认值,所以将其置为了NULL,函数原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
(2)若将线程属性指定为特定的值,就需要指定一个线程属性变量pthread_attr_t,这种变量需要初始化后才能使用,有如下初始化与销毁属性方法:
- int pthread_attr_init(pthread_attr_t *attr);
- int pthread_attr_destroy(pthread_attr_t *attr);
一旦初始化了一个属性变量之后,该线程属性变量就包含了线程的多种属性的初始值。使用下面的函数可以做出相应的修改:
(3)获取与设置分离属性
- int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
- int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
1)第一个参数attr是初始化的线程属性变量。
2)通过第二个参数detachstate可以更改,有两个可选值:PTHREAD_CREATE_DETACHED(表示线程属性是分离的),PTHREAD_CREATE_JOINABLE(表示线程属性不是分离的,默认值)
3)若将线程属性设置为PTHREAD_CREATE_DETACHED,那么即使线程退出的时候,而调用者线程没有调用pthread_join,这个线程也不会处于僵线程状态。(而之前我们是通过调用pthread_detach函数实现分离的)
(4)获取与设置栈大小
- int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
- int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
通常,第二个参数stacksize,可以指定为0,表示由系统定义栈大小。
(5)获取与设置栈溢出保护区大小
- int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
- int pthread_attr_getguardsize(ptherad_attr_t *attr, size_t *guardsize);
(6)获取与设置线程竞争范围
- int pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope);
- int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
(7)获取与设置调度策略
- int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
- int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
(8)获取与设置继承的调度策略
- int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
- int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
(9)获取与设置调度参数
- int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
- int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
使用上述函数,查看线程的各个属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include <unistd.h> #include <sys/types.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { pthread_attr_t attr; pthread_attr_init(&attr); int state; pthread_attr_getdetachstate(&attr, &state); if (state == PTHREAD_CREATE_JOINABLE) printf("detachstate:PTHREAD_CREATE_JOINABLE\n"); else if (state == PTHREAD_CREATE_DETACHED) printf("detachstate:PTHREAD_CREATE_DETACHED"); size_t size; pthread_attr_getstacksize(&attr, &size); printf("stacksize:%d\n", size); pthread_attr_getguardsize(&attr, &size); printf("guardsize:%d\n", size); int scope; pthread_attr_getscope(&attr, &scope); if (scope == PTHREAD_SCOPE_PROCESS) printf("scope:PTHREAD_SCOPE_PROCESS\n"); if (scope == PTHREAD_SCOPE_SYSTEM) printf("scope:PTHREAD_SCOPE_SYSTEM\n"); int policy; pthread_attr_getschedpolicy(&attr, &policy); if (policy == SCHED_FIFO) printf("policy:SCHED_FIFO\n"); else if (policy == SCHED_RR) printf("policy:SCHED_RR\n"); else if (policy == SCHED_OTHER) printf("policy:SCHED_OTHER\n"); int inheritsched; pthread_attr_getinheritsched(&attr, &inheritsched); if (inheritsched == PTHREAD_INHERIT_SCHED) printf("inheritsched:PTHREAD_INHERIT_SCHED\n"); else if (inheritsched == PTHREAD_EXPLICIT_SCHED) printf("inheritsched:PTHREAD_EXPLICIT_SCHED\n"); struct sched_param param; pthread_attr_getschedparam(&attr, ¶m); printf("sched_priority:%d\n", param.sched_priority); pthread_attr_destroy(&attr); return 0; } |
2.并发级别
(1)获取与设置并发级别
- int pthread_setconcurrency(int new_level);
- int pthread_getconcurrency(void);
(2)仅在N:M线程模型中有效,设置并发级别,给内核一个提示:表示提供给定级别数量的核心线程来映射用户线程是高效的。
3.线程特定数据
在单线程程序中,我们经常要用到“全局变量”以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。
(1)POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据成为Thread-specific Data, 或TSD。
线程特定数据模型如下:
1)“其他线程信息”包括线程控制块、CPU状态等信息;
2)另外除了“其他线程信息”之外,每个线程还有自己的特定数据——总共有128项(图中pkey[0]-pkey[127]所示)
3)如果需要使用这128项中的一个,首先需要去查找该key的指针是否为空的,如果是空的就可以使用,比如在该指针中存放数据a;但是,多个线程中,虽然每个线程的该指针都被使用了(如图中pkey[1],一旦一个线程中的key被创建,则所有线程都创建了该key,类似于创建的key是个全局变量),但是各个线程在该指针中存放的“实际数据”却不相同。
4)这样一来,线程0对该“实际数据”进程修改,并不会影响线程n中对应的“实际数据”。
(2)相关函数
- 创建一个key
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
第二个参数提供一个函数指针,来销毁“实际数据”。
示例,创建一个key:
pthread_key_t key_tsd;
pthread_key_create(&key_tsd, destroy_routinue);
void destroy_routine(void *value)
{
….
}
- 删除key
int pthread_key_delete(pthread_key_t key);
示例,删除上述创建的key:
pthread_key_delete(key_tsd);
- 获取线程特定数据
void *pthread_getspecific(pthread_key_t key);
- 设置线程特定数据
int pthread_setspecific(pthread_key_t key, const void *value);
示例,给线程设定特定数据:
…
typedef struct std { //自定义要设定的“特定数据”
pthread_t tid;
char *str;
}tsd_t;
tsd_t *value = (tsd_t*)malloc(sizeof(std_t));
value->tid = pthread_self();
value->str = (char*)arg;
pthread_setspecific(key_tsd, value);
…
使用上述函数实现一个程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <unistd.h> #include <sys/types.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) typedef struct tsd { pthread_t tid; char *str; }tsd_t; pthread_key_t key_tsd; void destroy_routine(void *value) { printf("destroy...\n"); free(value); } void * thread_routine(void *arg) { tsd_t *value = (tsd_t*)malloc(sizeof(tsd_t)); value->tid = pthread_self(); value->str = (char*)arg; pthread_setspecific(key_tsd,value); printf("%s setspecific %p\n", (char*)arg, value); value = pthread_getspecific(key_tsd); printf("tid = 0x%x str = %s\n", value->tid, value->str); sleep(2); value = pthread_getspecific(key_tsd); printf("tid = 0x%x str = %s\n", value->tid, value->str); return NULL; } int main(void) { pthread_key_create(&key_tsd, destroy_routine); pthread_t tid1; pthread_t tid2; pthread_create(&tid1, NULL, thread_routine, "thread1"); pthread_create(&tid2, NULL, thread_routine, "thread2"); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_key_delete(key_tsd); return 0; } |
输出结果:
1 2 3 4 5 6 7 8 9 |
[root@VM_198_209_centos 38_demon_tsd]# ./tsd thread2 setspecific 0x7feb1c0008c0 tid = 0x24300700 str = thread2 thread1 setspecific 0x7feb140008c0 tid = 0x24b01700 str = thread1 tid = 0x24300700 str = thread2 destroy... tid = 0x24b01700 str = thread1 destroy... |
最后补充一个函数:
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
pthread_once_t one_control = PTHREAD_ONCE_INIT;
该函数表示,第二个参数,函数指针指向的函数的执行,只在第一个线程进入的时候被执行一次!
例如将创建key的函数pthread_key_create放入线程入口函数thread_routine中,这样一来,每次创建一个线程都会调用一次thread_routine,进而会指向该函数中的pthread_key_create两次。而我们又只想它只创建一次key,这个时候就可以使用pthread_once函数来解决这个问题:
…
pthread_once_t once_control = PTHREAD_ONCE_INIT;//先定义一个once_control
…
//再在创建线程的入口函数thread_routine中调用该函数
pthread_once(&once_control, once_routine);
//该函数也要提供一个处理函数,将“动作”放入该函数中,本例是将创建key的函数放入
void once_routine(void)
{
pthread_key_create(&key_tsd, destroy_routine);
}
这样,当第一个线程调用过一次pthread_once之后,第二个线程进来后发现该函数已被调用过一次,就不会再次调用了。因此pthread_key_create函数只执行了一次,解决问题。