本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互。
1、线程限制:
Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询。和其它的限制使用目的一样,为了应用程序的在不同操作 系统的可移植性。 一些限制:
PTHREAD_DESTRUCTOR_ITERATIONS: 销毁一个线程数据最大的尝试次数,可以通过_SC_THREAD_DESTRUCTOR_ITERATIONS作为sysconf的参数查询。
PTHREAD_KEYS_MAX: 一个进程可以创建的最大key的数量。可以通过_SC_THREAD_KEYS_MAX参数查询。
PTHREAD_STACK_MIN: 线程可以使用的最小的栈空间大小。可以通过_SC_THREAD_STACK_MIN参数查询。
PTHREAD_THREADS_MAX:一个进程可以创建的最大的线程数。可以通过_SC_THREAD_THREADS_MAX参数查询
2、线程属性
在调用pthread_create函数创建一个新线程时候可以指定线程的属性,属性类型为pthread_attr_t,该结构对应用程序是不透明,操作函数如下:
int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr); //释放线程属性空间
线程属性主要有:(1)线程的分离状态属性detachstate,(2)线程栈末尾的警戒缓冲区大小guardsize,(3)线程栈的最低地址statckaddr,(4)线程栈的大小stacksize。
如果对现有某个线程的终止状态不感兴趣的话,可以使用pthread_detach函数让操作系统在线程退出时候收回它所占用的资源。创建线程时候可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。可以使用下面函数进程操作分离属性:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
detatchstate取值为:(1)PTHREAD_CREATE_DETACHED 分离状态启动,(2)PTHREAD_CREATE_JOINABLE 正常启动,应用程序可以获取线程的终止状态。
线程栈属性操作函数:
int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize);
int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
写一个程序,创建一个线程,设置其属性,然后获取属性并输出。程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <pthread.h>
6
7 void * thread_func(void *arg)
8 {
9 pthread_t pid;
10 pthread_attr_t attr;
11 int i;
12 size_t v;
13 void *stkaddr;
14 pid = pthread_self();
15 pthread_getattr_np(pthread_self(), &attr);
16 pthread_attr_getdetachstate(&attr, &i);
17 printf("Detachstate =");
18 if(i == PTHREAD_CREATE_DETACHED)
19 printf("PTHREAD_CREATE_DETACHED\\n");
20 else if(i == PTHREAD_CREATE_JOINABLE)
21 printf("PTHREAD_CREATE_JOINABLE\\n");
22 pthread_attr_getguardsize(&attr, &v);
23 printf("Guard size = %d bytes\\n",v);
24 pthread_attr_getstack(&attr, &stkaddr, &v);
25 printf("Stack address = %p\\n", stkaddr);
26 printf("Stack size = 0x%x bytes\\n", v);
27
28 return ((void*)0);
29 }
30
31 int main()
32 {
33 pthread_t pid;
34 pthread_attr_t attr;
35 int err;
36 err = pthread_attr_init(&attr);
37 if(err != 0)
38 {
39 perror("pthread_attr_init() error");
40 exit(-1);
41 }
42 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
43 pthread_create(&pid,&attr,thread_func,NULL);
44 sleep(2);
45 }
程序执行结果如下:
3、同步属性
(1)互斥量属性:有进程共享属性和类型属性两种,进程共享属性是可选的,互斥量属性数据类型为pthread_mutexattr_t。在进程中,多个线程可以访问同一个同步对象,默认情况进程共享互斥量属性为:PTHREAD_PROCESS_PRIVATE。互斥量属性操作函数如下:
int pthread_mutexattr_init(pthread_mutexattr_t *attr); //初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); //回收
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared); //查询进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared); //设置进程共享属性
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type); //查询类型属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); //设置类型属性
类型互斥量控制着互斥量的特性,POSIX.1定义了四种类型:
互斥量类型 | 用途 | 没有解锁时再次加锁 | 不占用是解锁 | 在已解锁是解锁 |
PTHREAD_MUTEX_NORMAL | 标准类型,不做任何检查 | 死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK | 进程错误检查 | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE | 避免死锁 | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFFAULT | 请求默认语义 | 未定义 | 未定义 | 未定义 |
PTHREAD_MUTEX_RECURSIVE互斥量类型允许在同一个线程在互斥量解锁之前对该互斥量进程多次加锁,当对个一个量加锁两次的时候,可以避免死锁。例如用默认类型,当对一个锁加锁两次时候,会造成死锁,例如下面的程序:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/types.h>
6 #include <pthread.h>
7
8 struct foo
9 {
10 int f_count;
11 int f_addtimes;
12 pthread_mutex_t f_mutex;
13 };
14
15 struct foo * foo_alloc()
16 {
17 struct foo* fp;
18 fp = (struct foo*)malloc(sizeof(struct foo));
19 if(fp != NULL)
20 {
21 fp->f_count = 0;
22 fp->f_addtimes = 0;
23 pthread_mutex_init(&fp->f_mutex,NULL);
24 }
25 return fp;
26 }
27
28 void foo_addtimes(struct foo *fp)
29 {
30 pthread_mutex_lock(&fp->f_mutex);
31 fp->f_addtimes++;
32 pthread_mutex_unlock(&fp->f_mutex);
33 }
34
35 void foo_add(struct foo *fp) //调用foo_addtimes对f_mutex加锁两次
36 {
37 pthread_mutex_lock(&fp->f_mutex);
38 fp->f_count++;
39 foo_addtimes(fp);
40 pthread_mutex_unlock(&fp->f_mutex);
41 }
42
43 void * thread_func1(void *arg)
44 {
45 struct foo *fp = (struct foo*)arg;
46 printf("thread 1 start.\\n");
47 foo_add(fp); //调用函数执行,造成死锁
48 printf("in thread 1 count = %d\\n",fp->f_count);
49 printf("thread 1 exit.\\n");
50 pthread_exit((void*)1);
51 }
52
53 int main()
54 {
55 pthread_t pid1;
56 int err;
57 void *pret;
58 struct foo *fobj;
59 fobj = foo_alloc();
60 pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
61 pthread_join(pid1,&pret);
62 printf("thread 1 exit code is: %d\\n",(int)pret);
63 exit(0);
64 }
执行结果如下:
从结果可以看出程序执行到foo_add()时候陷入了死锁。因为foo_add函数调用foo_addtimes函数,使得加锁两次,导致死锁。解决这个问题可以将foo_addtimes函数中的锁去掉,或将foo_addtimes函数合并到foo_add中。除了这些办法,还可以设置互斥量属性,设置为递归锁。程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/types.h>
6 #include <pthread.h>
7
8 struct foo
9 {
10 int f_count;
11 int f_addtimes;
12 pthread_mutex_t f_mutex;
13 };
14 struct foo * foo_alloc()
15 {
16 struct foo* fp;
17 fp = (struct foo*)malloc(sizeof(struct foo));
18 if(fp != NULL)
19 {
20 fp->f_count = 0;
21 fp->f_addtimes = 0;
22 //设置互斥量类型为递归锁,可以对已加锁再次加锁
23 pthread_mutexattr_t attr;
24 pthread_mutexattr_init(&attr);
25 pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
26 pthread_mutex_init(&fp->f_mutex,&attr);
27 }
28 return fp;
29 }
30 void foo_addtimes(struct foo *fp)
31 {
32 pthread_mutex_lock(&fp->f_mutex);
33 fp->f_addtimes++;
34 pthread_mutex_unlock(&fp->f_mutex);
35 }
36
37 void foo_add(struct foo *fp)
38 {
39 pthread_mutex_lock(&fp->f_mutex);
40 fp->f_count++;
41 foo_addtimes(fp);
42 pthread_mutex_unlock(&fp->f_mutex);
43 }
44 void * thread_func1(void *arg)
45 {
46
47 struct foo *fp = (struct foo*)arg;
48 printf("thread 1 start.\\n");
49 foo_add(fp);
50 printf("in thread 1 count = %d\\n",fp->f_count);
51 printf("thread 1 exit.\\n");
52 pthread_exit((void*)1);
53 }
54 void * thread_func2(void *arg)
55 {
56
57 struct foo *fp = (struct foo*)arg;
58 printf("thread 2 start.\\n");
59 foo_add(fp);
60 printf("in thread 2 count = %d\\n",fp->f_count);
61 printf("thread 2 exit.\\n");
62 pthread_exit((void*)2);
63 }
64 int main()
65 {
66 pthread_t pid1,pid2;
67 int err;
68 void *pret;
69 struct foo *fobj;
70 fobj = foo_alloc();
71 pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
72 pthread_create(&pid2,NULL,thread_func2,(void*)fobj);
73 pthread_join(pid1,&pret);
74 printf("thread 1 exit code is: %d\\n",(int)pret);
75 pthread_join(pid2,&pret);
76 printf("thread 2 exit code is: %d\\n",(int)pret);
77 exit(0);
78 }
程序执行结果如下:
(2)读写锁属性:与互斥量类似,但是只支持进程共享唯一属性,操作函数原型如下:
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);
(3)条件变量属性:也是只支持进程共享属性,操作函数原型如下:
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
4、重入
有了信号处理程序和多线程,多个控制线程在同一时间可能潜在的调用同一个函数,如果一个函数在同一时刻可以被多个线程安全调用,则称为函数是线程安全的。很多函数并不是线程安全的,因为它们返回的数据是存放在静态的内存缓冲区,可以通过修改接口,要求调用者自己提供缓冲区使函数变为线程安全的。POSIX.1提供了以安全的方式管理FILE对象的方法,使用flockfile和ftrylockfile获取与给定FILE对象关联的锁。这个锁是递归锁。函数原型如下:
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
为了避免标准I/O在一次一个字符操作时候频繁的获取锁开销,出现了不加锁版本的基于字符的标准I/O例程。函数如下:
int getc_unlocked(FILE *stream);
int getchar_unlocked(void);
int putc_unlocked(int c, FILE *stream);
int putchar_unlocked(int c);
实现getenv函数,不可重入版本(因为调用getenv的线程返回的字符串都存放在同一个静态缓冲区中),程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <pthread.h>
6 #include <limits.h>
7 #include <string.h>
8 static char envbuf[1024]; //值存放到一个静态缓冲区中,线程共享
9 extern char **environ;
10 char *mygetenv(const char *name)
11 {
12 int i,len;
13 len = strlen(name);
14 for(i=0;environ[i] != NULL;i++)
15 {
16 if((strncmp(name,environ[i],len) == 0) &&
17 (environ[i][len] == \'=\'))
18 {
19 strcpy(envbuf,&environ[i][len+1]);
20 return envbuf;
21 }
22 }
23 return NULL;
24 }
25 void * thread_func1(void *arg)
26 {
27 char *pvalue;
28 printf("thread 1 start.\\n");
29 pvalue = mygetenv("HOME");
30 printf("HOME=%s\\n",pvalue);
31 printf("thread 1 exit.\\n");
32 pthread_exit((void*)1);
33 }
34 void * thread_func2(void *arg)
35 {
36 char *pvalue;
37 printf("thread 2 start.\\n");
38 pvalue = mygetenv("SHELL");
39 printf("SHELL=%s\\n",pvalue);
40 printf("thread 2 exit.\\n");
41 pthread_exit((void*)2);
42 }
43 int main()
44 {
45 pthread_t pid1,pid2;
46 int err;
47 void *pret;
48 pthread_create(&pid1,NULL,thread_func1,NULL);
49 pthread_create(&pid2,NULL,thread_func2,NULL);
50 pthread_join(pid1,&pret);
51 printf("thread 1 exit code is: %d\\n",(int)pret);
52 pthread_join(pid2,&pret);
53 printf("thread 2 exit code is: %d\\n",(int)pret);
54 exit(0);
55 }
从结果可以看出,多次执行结果,可能会发现SHELL环境变量的值被HOME环境变量的值覆盖了。这是因为共用一个静态存储变量,而两个线程执行先后顺序不同,导致结果可能被覆盖。可以修改接口,调用者提供自己的缓冲区,每个线程可以使用各自的不同的缓冲区从而避免其他线程的干扰。改进的getenv程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <pthread.h>
6 #include <limits.h>
7 #include <string.h>
8
9 extern char **environ;
10 pthread_mutex_t env_mutex;
11 static pthread_once_t init_done = PTHREAD_ONCE_INIT;
12 //初始化互斥量类型
13 static void thread_init(void)
14 {
15 pthread_mutexattr_t attr;
16 pthread_mutexattr_init(&attr);
17 //设置为递归锁
18 pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
19 pthread_mutex_init(&env_mutex,&attr);
20 pthread_mutexattr_destroy(&attr);
21 }
22 int mygetenv(const char *name,char *buf,int buflen)
23 {
24 int i,len,olen;
25 pthread_once(&init_done,thread_init); //初始化互斥量
26 len = strlen(name);
27 //加锁防止name被修改
28 pthread_mutex_lock(&env_mutex);
29 for(i=0;environ[i] != NULL;i++)
30 {
31 if((strncmp(name,environ[i],len) == 0) &&
32 (environ[i][len] == \'=\'))
33 {
34 olen = strlen(&environ[i][len]);
35 if(olen >= buflen)
36 {
37 pthread_mutex_unlock(&env_mutex);
38 return ENOSPC;
39 }
40 strcpy(buf,&environ[i][len+1]);
41 pthread_mutex_unlock(&env_mutex);
42 return 0;
43 }
44 }
45 pthread_mutex_unlock(&env_mutex);
46 return ENOENT;
47 }
48
49 void * thread_func1(void *arg)
50 {
51 char buf[100];
52 printf("thread 1 start.\\n");
53 mygetenv("HOME",buf,100);
54 printf("HOME=%s\\n",buf);
55 printf("thread 1 exit.\\n");
56 pthread_exit((void*)1);
57 }
58 void * thread_func2(void *arg)
59 {
60 char buf[100];
61 printf("thread 2 start.\\n");
62 mygetenv("SHELL",buf,100);
63 printf("SHELL=%s\\n",buf);
64 printf("thread 2 exit.\\n");
65 pthread_exit((void*)2);
66 }
67 int main()
68 {
69 pthread_t pid1,pid2;
70 int err;
71 void *pret;
72 pthread_create(&pid1,NULL,thread_func1,NULL);
73 pthread_create(&pid2,NULL,thread_func2,NULL);
74 pthread_join(pid1,&pret);
75 printf("thread 1 exit code is: %d\\n",(int)pret);
76 pthread_join(pid2,&pret);
77 printf("thread 2 exit code is: %d\\n",(int)pret);
78 exit(0);
79 }
程序执行结果如下:
从结果可以发现,每个线程提供自己的缓冲区,保证了结果的正确性。
5、线程似有数据
线程似有数据时存储和查询与某个线程相关的数据的一种机制,希望每个线程可以独立的访问数据副本,而不需要担心与其他线程的同步访问问题。进程中的所有线程都可以访问进程的整个地址空间,除了使用寄存器以外,线程没有办法阻止其他线程访问它的数据,线程似有数据也不例外。管理线程私有数据的函数可以提高线程间的数据独立性。
分配线程私有数据过程:首先调用pthread_key_create创建与该数据关联的键,用于获取对线程私有数据的访问权,这个键可以被进程中所有线程访问,但是每个线程把这个键与不同的线程私有数据地址进行关联然后通过调用pthread_setspecific函数吧键和线程私有数据关联起来,可以通过pthread_getspecific函数获取线程私有数据的地址。
pthread_key_create函数可以选择为该键关联的析构函数,调用pthread_key_delete函数来取消与线程私有数据值之间的关联关系。通过调用pthread_once函数确保分配的键并不会由于在初始化阶段的竞争而发生变动。
操作函数如下:
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); //避免竞争条件
pthread_once_t once_control = PTHREAD_ONCE_INIT;
void *pthread_getspecific(pthread_key_t key); //返回线程私有数据值,没有返回NULL
int pthread_setspecific(pthread_key_t key, const void *value); //设置线程私有数据
现在使用线程私有数据来维护每个线程的数据缓冲区,来实现getenv,程序如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <pthread.h>
6 #include <string.h>
7
8 extern char **environ;
9 static pthread_key_t key;
10 pthread_mutex_t env_mutex;
11 static pthread_once_t init_done = PTHREAD_ONCE_INIT;
12 static void thread_init(void)
13 {
14 pthread_mutexattr_t attr;
15 pthread_mutexattr_init(&attr);
16 pthread_mutexattr_settype(&以上是关于Unix环境高级编程线程控制的主要内容,如果未能解决你的问题,请参考以下文章