《APUE》读书笔记—第十二章线程控制
本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互。
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(&attr,PTHREAD_MUTEX_RECURSIVE); 17 pthread_mutex_init(&env_mutex,&attr); 18 pthread_mutexattr_destroy(&attr); 19 pthread_key_create(&key,free); 20 } 21 char* mygetenv(const char *name) 22 { 23 int i,len; 24 char *envbuf; 25 //调用thread_inti一次 26 pthread_once(&init_done,thread_init); 27 pthread_mutex_lock(&env_mutex); 28 //获取线程中与key关联的私有数据 29 envbuf = (char*)pthread_getspecific(key); 30 if(envbuf == NULL) 31 { 32 envbuf = (char*)malloc(100); 33 if(envbuf == NULL) 34 { 35 pthread_mutex_unlock(&env_mutex); 36 return NULL; 37 } 38 //将envbuf设置为与key关联 39 pthread_setspecific(key,envbuf); 40 } 41 len = strlen(name); 42 for(i=0;environ[i] != NULL;i++) 43 { 44 if((strncmp(name,environ[i],len) == 0) && 45 (environ[i][len] == '=')) 46 { 47 48 strcpy(envbuf,&environ[i][len+1]); 49 pthread_mutex_unlock(&env_mutex); 50 return envbuf; 51 } 52 } 53 pthread_mutex_unlock(&env_mutex); 54 return NULL; 55 } 56 void * thread_func1(void *arg) 57 { 58 char *pvalue; 59 printf("thread 1 start.\n"); 60 pvalue = mygetenv("HOME"); 61 printf("HOME=%s\n",pvalue); 62 printf("thread 1 exit.\n"); 63 pthread_exit((void*)1); 64 } 65 void * thread_func2(void *arg) 66 { 67 char *pvalue; 68 printf("thread 2 start.\n"); 69 pvalue = mygetenv("SHELL"); 70 printf("SHELL=%s\n",pvalue); 71 printf("thread 2 exit.\n"); 72 pthread_exit((void*)2); 73 } 74 75 int main() 76 { 77 pthread_t pid1,pid2; 78 int err; 79 void *pret; 80 pthread_create(&pid1,NULL,thread_func1,NULL); 81 pthread_create(&pid2,NULL,thread_func2,NULL); 82 pthread_join(pid1,&pret); 83 printf("thread 1 exit code is: %d\n",(int)pret); 84 pthread_join(pid2,&pret); 85 printf("thread 2 exit code is: %d\n",(int)pret); 86 exit(0); 87 }
程序执行结果如下:
这个版本的getenv是线程安全的,但不是异步-信号安全的,因为调用了malloc函数,malloc函数本身不是异步-信号安全的。
6、取消选项
取消选项包括可取消状态和可取消类型,针对线程在响应pthread_cancel函数调用时候所呈现的行为。可取消状态取值为:PTHREAD_CANCLE_ENABLE (默认的可取消状态)或PTHREAD_CANCLE_DISABLE。取消类型也称为延迟取消,类型可以为:PTHREAD_CANCLE_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。通过下面函数进行设置取消状态和取消类型:
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
void pthread_testcancel(void); //自己添加取消点
7、线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。进程中的信号是传递到单个线程的,进程中的信号屏蔽函数sigprocmask函数在线程中没有定义,线程中必须使用pthread_sigmask。线程可以调用sigwait函数等待一个或者多个信发送。调用pthread_kill函数将信号发送到线程。具体函数原型如下:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
int sigwait(const sigset_t *set, int *sig);
int pthread_kill(pthread_t thread, int sig);
采用以上函数实现线程间同步信号处理,程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <pthread.h> 6 #include <signal.h> 7 8 int quitflag; 9 sigset_t mask; 10 11 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 12 pthread_cond_t wait = PTHREAD_COND_INITIALIZER; 13 14 void *thread_func(void *arg) 15 { 16 int err,signo; 17 for(;;) 18 { 19 err = sigwait(&mask,&signo); 20 if(err!= 0) 21 { 22 perror("sigwait error.\n"); 23 exit(-1); 24 } 25 switch(signo) 26 { 27 case SIGINT: 28 printf("\nInterrupt.\n"); 29 break; 30 case SIGQUIT: 31 pthread_mutex_lock(&lock); 32 printf("\nQuit.\n"); 33 quitflag = 1; 34 pthread_mutex_unlock(&lock); 35 pthread_cond_signal(&wait); 36 return 0; 37 default: 38 printf("unexpected siganl %d\n",signo); 39 exit(1); 40 } 41 } 42 } 43 int main() 44 { 45 int err; 46 sigset_t oldmask; 47 pthread_t tid; 48 sigemptyset(&mask); 49 sigaddset(&mask,SIGINT); 50 sigaddset(&mask,SIGQUIT); 51 pthread_sigmask(SIG_BLOCK,&mask,&oldmask); 52 pthread_create(&tid,NULL,thread_func,NULL); 53 pthread_mutex_lock(&lock); 54 while(quitflag == 0) 55 pthread_cond_wait(&wait,&lock); 56 pthread_mutex_unlock(&lock); 57 quitflag = 0; 58 sigprocmask(SIG_SETMASK,&oldmask,NULL); 59 exit(0); 60 }
程序执行结果如下:
8、线程和fork
父进程调用fork为子进程创建了整个进程地址空间的副本,子进程从父进程那里继承了所有互斥量、读写锁和条件变量的状态。如果父进程包括多个线程,子进程在fork返回以后,如果紧接着不马上调用exec的话,就需要清理锁。在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的,父进程中的线程占有锁,则子进程同样占有锁,但是子进程不包含占有锁的线程的副本。通过pthread_atfork函数建立fork处理程序清除锁状态。函数原型如下:
int pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
prepare处理程序由父进程在fork创建子进程前调用,获取父进程定义的所有锁。parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境调用,对prepare处理程序获得的所有锁进行解锁,child处理程序在fork返回之前在子进程环境中调用,也必须释放prepare处理程序获得的所有锁。parent和child处理程序与它们注册时顺序相同,prepare处理程序调用则与注册时的顺序相反。
写个程序演示如何使用pthread_atfork和fork处理程序。程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <pthread.h> 6 #include <signal.h> 7 8 pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER; 9 pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER; 10 11 void prepare(void) 12 { 13 printf("preparing locks...\n"); 14 pthread_mutex_lock(&lock1); 15 pthread_mutex_lock(&lock2); 16 } 17 void parent(void) 18 { 19 printf("parent unlocking locks...\n"); 20 pthread_mutex_unlock(&lock1); 21 pthread_mutex_unlock(&lock2); 22 } 23 void child(void) 24 { 25 printf("child unlocking locks...\n"); 26 pthread_mutex_unlock(&lock1); 27 pthread_mutex_unlock(&lock2); 28 } 29 void* thread_func(void *arg) 30 { 31 printf("thread started...\n"); 32 pause(); 33 return 0; 34 } 35 int main() 36 { 37 pid_t pid; 38 pthread_t tid; 39 pthread_atfork(prepare,parent,child); 40 pthread_create(&tid,NULL,thread_func,NULL); 41 sleep(2); 42 printf("parent about to fork.\n"); 43 pid = fork(); 44 if(pid == -1) 45 { 46 perror("fork() error"); 47 exit(-1); 48 } 49 if(pid == 0) 50 printf("child returned from fork.\n"); 51 else 52 printf("parent returned form fork.\n"); 53 exit(0); 54 }
程序执行结果如下: