Unix环境高级编程(十二)线程控制
本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互。
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 }
程序执行结果如下:
Unix环境高级编程(十二)线程控制的更多相关文章
- (十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (九) 一起学 Unix 环境高级编程 (APUE) 之 线程
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- 《UNIX环境高级编程》笔记--线程的标识、创建和终止
1.线程标识 就像每个进程都有一个进程ID一样,每个线程都有一个线程ID.进程ID在整个系统中是唯一的,但线程ID只在它所属的 进程环境中有效. 线程ID使用pthread_t数据类型来表示,实现的时 ...
- Unix环境高级编程(十五)高级I/O
1.非阻塞I/O 对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道.终端设备和网络设备).(2)如果数据不能立 ...
- UNIX环境高级编程笔记之线程
本章涉及到线程的一些基本知识点,讨论了现有的创建线程和销毁线程的POSIX.1原语,此外,重点介绍了线程同步问题,讨论了三种基本的同步机制:互斥量.读写锁.条件变量.
- UNIX环境高级编程笔记之进程控制
本章重点介绍了进程控制的几个函数:fork.exec族._exit.wait和waitpid等,主要需要掌握的是父进程和子进程之间的运行机制,怎么处理进程的正常和异常终止.以及怎么让进程执行不同的程序 ...
- Unix环境高级编程(十九)终端I/O
终端I/O应用很广泛,用于终端.计算机之间的直接连线.调制解调器以及打印机等等.终端I/O有两种不同的工作模式: (1)规范模式输入处理:终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返 ...
- Unix环境高级编程(十八)高级进程间通信
本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符.服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信. 1 ...
随机推荐
- java类过滤器,防止页面SQL注入
package com.tarena.dingdang.filter; import java.io.IOException; import java.util.Enumeration; import ...
- 眼前一亮!十八款新潮而又独特的网站Header设计
一个网站最重要的一个部分就是标题.这将是访问者的第一印象,设计出一个吸引眼球并且功能清晰导航还是比较容易的,但想出一个独特的header却总是困难的,这就是为什么我决定把那些对我有最影响的导航列出来了 ...
- Inside GDALAllRegister之二: 自动加载驱动
代码 GetGDALDriverManager()->AutoLoadDrivers(); 包含了两部分: 首先获得GDALDriverManager的singleton对象的指针,这点之 ...
- 关于COM组件log的位置
进程内组件写的log,如果不指定路径直接提供文件名,log文件的位置在dll所在的目录中. 进程外组件写的log,如果不指定路径直接提供文件名,log文件的位置不在exe所在的目录中,而是在系统目录. ...
- com.esotericsoftware.kryo.kryoexception java.util.ConcurentModificationException
近期 有网友看我的"整合Kafka到Spark Streaming--代码演示样例和挑战"文章, 讲 kafka对象 放到 pool 并通过broadcast广播出去: 然后 在开 ...
- STM8S AD转换
//不说那么多了,直接上程序 void ADC1_DeInit(void) { ADC1->CSR = ADC1_CSR_RESET_VALUE; ADC1->CR1 = ADC1_CR1 ...
- GIS专业书籍、文档、数据、网站、工具等干货
整理.分享一些个人整理的GIS专业书籍.文档.数据.网站.工具等.也希望大家将自己的心得也分享出来,一起交流,共同进步. 如果下载链接失效,请到这里去:地信网 一.原理应用类 GIS基础类 01.地理 ...
- CheeseZH: Octave basic commands
1.Basic Operations 5+6 3-2 5*8 1/2 2^6 1 == 2 %false ans = 0 1 ~= 2 %true ans = 1 1 && 0 %AN ...
- UIScrollView 图片循环滚动
1:假如有6个图片:那个,Scrollview的大小加 7 个图片的大小 2: //ImageScrollView; UIScrollView *imageScroll = [[UIScrollVie ...
- 使用ADS1.2的注意事项及常用技巧
如果创建的项目中有多个文件时(尤其是编译后的镜像大小超过4K时),一定要在link order栏下调整文件顺序,主要是前几个文件的顺序(2440init.s.2440slib.s.nand.c这三个文 ...