Linux/UNIX线程(1)
Linux/UNIX线程(1)
线程(1)
本文将介绍如何使用多个控制线程在单个进程环境中执行多个任务。
一个进程中的所有线程都可以访问该进程的组成部件(如文件描述符和内存)。
线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
线程标识
进程ID在整个系统中是唯一的。每个线程ID,线程ID只在它所属的进程环境中有效。
在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。
#include<pthread.h>
intpthread_equal(pthread_t t1, pthread_t t2);
返回值:若相等则返回非0值,否则返回0。
线程可以通过调用
#include<pthread.h>
pthread_tpthread_self(void);
返回值是调用线程的线程。
线程创建
创建线程可以调用pthread_create函数创建。
#include<pthread.h>
intpthread_create(pthread_t *thread, const pthread_attr_t *attr,
void*(*start_routine) (void *), void *arg);
返回值:若成功则返回0,否则返回错误编号
当pthread_create成功返回时,由thread指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性。将attr设置为NULL,创建默认属性的线程。
新创建的线程从start_routine函数地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但该线程的未决信号集被清除。
下面的程序创建了一个线程并且打印进程ID、新线程的线程ID以及初始化线程的线程ID。
[cpp] view plaincopyprint?01.#include <stdio.h>
02.#include <stdlib.h>
03.#include <pthread.h>
04.
05.pthread_t ntid;
06.
07.void printids(const char *s)
08.{
09. pid_t pid;
10. pthread_t tid;
11.
12. pid = getpid();
13. tid = pthread_self();
14. printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
15. (unsigned int)tid, (unsigned int)tid);
16.}
17.
18.void * thr_fn(void *arg)
19.{
20. printids("new thread: ");
21.return((void *)0);
22.}
23.
24.void err_quit(const char* fmt, ...)
25.{
26.printf("%s\n",fmt);
27.exit(1);
28.}
29.
30.
31.int main(void)
32.{
33. int err;
34.
35. err = pthread_create(&ntid, NULL, thr_fn, NULL);
36. if (err != 0)
37. err_quit("can't create thread: %s\n", strerror(err));
38.printids("main thread:");
39. sleep(1);
40. exit(0);
41.}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t ntid;
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
(unsigned int)tid, (unsigned int)tid);
}
void * thr_fn(void *arg)
{
printids("new thread: ");
return((void *)0);
}
void err_quit(const char* fmt, ...)
{
printf("%s\n",fmt);
exit(1);
}
int main(void)
{
int err;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
err_quit("can't create thread: %s\n", strerror(err));
printids("main thread:");
sleep(1);
exit(0);
}
编译:
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
gcc creatThread.c –lpthread
执行及输出:
./a.out
main thread: pid2846 tid 3079362240 (0xb78b56c0)
new thread: pid 2846 tid 3079359344 (0xb78b4b70)
主线程需要休眠,否则整个进程有可能在新线程进入运行之前就终止了。新线程是通过pthread_self()来获取自己的线程ID,而不是从共享内存中读出或者从线程的启动例程中以参数接收到。pthread_create函数会通过第一个参数返回新建线程的ID。在本例中,新建线程ID被放在ntid中,但是如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未初始化的ntid内容。
线程终止
如果进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。一次类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。
但线程可以通过下列三种方式退出:
1. 线程只是从启动例程中返回,返回值是线程的退出码
2. 线程可以被同一进程中的其他线程取消
3. 线程调用pthread_exit
pthread_exit函数:
#include<pthread.h>
voidpthread_exit(void *retval);
retval 是一个无类型的指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
#include<pthread.h>
intpthread_join(pthread_t thread, void **retval);
返回值:若成功则返回0,否则返回错误编号
调用pthread_join函数的线程将一直被阻塞,直到指定的线程调用pthread_exit 、从启动例程中返回或者被取消。如果线程只是从它的启动例程返回,retval 将包含返回码。如果线程被取消,retval 指定的内存单元就设置为PTHREAD_CANCELED。
[cpp] view plaincopyprint?01.#include<stdio.h>
02.#include<stdlib.h>
03.#include<pthread.h>
04.
05.void * thr_fn1(void*arg)
06.{
07. printf("thread 1 returning\n");
08. return((void *)1);
09.}
10.
11.void * thr_fn2(void*arg)
12.{
13. printf("thread 2 exiting\n");
14. pthread_exit((void *)2);
15.}
16.voiderr_quit(const char* fmt, ...)
17.{
18. printf("%s\n",fmt);
19. exit(1);
20.}
21.int main(void)
22.{
23. int err;
24. pthread_t tid1, tid2;
25. void *tret;
26.
27. err = pthread_create(&tid1, NULL,thr_fn1, NULL);
28. if (err != 0)
29. {
30. err_quit("can't create thread 1:%s\n", strerror(err));
31.
32. }
33. err = pthread_create(&tid2, NULL,thr_fn2, NULL);
34. if (err != 0)
35. err_quit("can't create thread 2:%s\n", strerror(err));
36.err = pthread_join(tid1, &tret);
37.if (err != 0)
38. err_quit("can'tjoin with thread 1: %s\n", strerror(err));
39. printf("thread 1 exitcode %d\n", (int)tret);
40. err = pthread_join(tid2,&tret);
41. if (err != 0)
42. err_quit("can'tjoin with thread 2: %s\n", strerror(err));
43. printf("thread 2 exitcode %d\n", (int)tret);
44. exit(0);
45.}
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void * thr_fn1(void*arg)
{
printf("thread 1 returning\n");
return((void *)1);
}
void * thr_fn2(void*arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
voiderr_quit(const char* fmt, ...)
{
printf("%s\n",fmt);
exit(1);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL,thr_fn1, NULL);
if (err != 0)
{
err_quit("can't create thread 1:%s\n", strerror(err));
}
err = pthread_create(&tid2, NULL,thr_fn2, NULL);
if (err != 0)
err_quit("can't create thread 2:%s\n", strerror(err));
err = pthread_join(tid1, &tret);
if (err != 0)
err_quit("can'tjoin with thread 1: %s\n", strerror(err));
printf("thread 1 exitcode %d\n", (int)tret);
err = pthread_join(tid2,&tret);
if (err != 0)
err_quit("can'tjoin with thread 2: %s\n", strerror(err));
printf("thread 2 exitcode %d\n", (int)tret);
exit(0);
}
运行及输出:
thread 2 exiting
thread 1 returning
thread 1 exit code 1
thread 2 exit code 2
可以看出当一个线程通过调用pthread_exit退出或简单地从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。
pthread_exit和pthread_create函数的无类型指针参数可能传递一个复杂的结构的地址,单数该结构所使用的内存调用者完成调用以后必须仍然是有效的。可以使用全局结构或者malloc函数分配栈结构。
线程可以通过调用pthread_cancel函数请求取消同一进程中的其他线程。
#include <pthread.h>
int pthread_cancel(pthread_t thread);
返回值:若成功则返回0,否则返回错误编号。
在默认情况下,pthread_cancel函数会使得由thread标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是线程可以选择忽略取消方式或是控制取消方式。
线程可以安排它退出时需要调用的函数,这与进程可以用atexit函数安排进程退出时需要调用的函数时类似的。这样的函数成为线程清理处理程序。线程可以建立多个清理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。
#include<pthread.h>
voidpthread_cleanup_push(void (*routine)(void *), void *arg);
voidpthread_cleanup_pop(int execute);
当线程执行以下动作时调用清理函数,调用参数为arg,清理函数routine的调用顺序是由pthread_cleanup_push函数安排的。
1.调用pthread_exit时
2.响应取消请求时
3.用非零execute参数调用voidpthread_cleanup_pop时
如果execute为0,清理函数将不被调用。无论哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。
如下程序显示了如何使用线程清理处理程序。
[cpp] view plaincopyprint?01.#include <stdio.h>
02.#include <stdlib.h>
03.#include <pthread.h>
04.
05.void cleanup(void *arg)
06.{
07. printf("cleanup: %s\n", (char *)arg);
08.}
09.
10.void * thr_fn1(void *arg)
11.{
12. printf("thread 1 start\n");
13. pthread_cleanup_push(cleanup, "thread 1 first handler");
14. pthread_cleanup_push(cleanup, "thread 1 second handler");
15. printf("thread 1 push complete\n");
16. if (arg)
17. return((void *)1);
18. pthread_cleanup_pop(0);
19. pthread_cleanup_pop(0);
20.return((void*)1);
21.}
22.
23.void * thr_fn2(void *arg)
24.{
25.printf("thread2 start\n");
26.pthread_cleanup_push(cleanup,"thread 2 first handler");
27.pthread_cleanup_push(cleanup,"thread 2 second handler");
28.printf("thread2 push complete\n");
29.if (arg)
30. pthread_exit((void *)2);
31.pthread_cleanup_pop(0);
32.pthread_cleanup_pop(0);
33.pthread_exit((void*)2);
34.}
35.
36.void err_quit(const char* fmt, ...)
37.{
38.printf("%s\n",fmt);
39.exit(1);
40.}
41.
42.int main(void)
43.{
44.int err;
45.pthread_t tid1, tid2;
46.void *tret;
47.
48.err =pthread_create(&tid1, NULL, thr_fn1, (void *)1);
49.if (err != 0)
50.err_quit("can't create thread 1: %s\n", strerror(err));
51. err =pthread_create(&tid2, NULL, thr_fn2, (void *)1);
52.if (err != 0)
53.err_quit("can't create thread 2: %s\n", strerror(err));
54.err =pthread_join(tid1, &tret);
55.if (err != 0)
56.err_quit("can't join with thread 1: %s\n", strerror(err));
57.printf("thread1 exit code %d\n", (int)tret);
58.err =pthread_join(tid2, &tret);
59.if (err != 0)
60.err_quit("can't join with thread 2: %s\n", strerror(err));
61.printf("thread2 exit code %d\n", (int)tret);
62.exit(0);
63.}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void * thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1 first handler");
pthread_cleanup_push(cleanup, "thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
return((void *)1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return((void*)1);
}
void * thr_fn2(void *arg)
{
printf("thread2 start\n");
pthread_cleanup_push(cleanup,"thread 2 first handler");
pthread_cleanup_push(cleanup,"thread 2 second handler");
printf("thread2 push complete\n");
if (arg)
pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)2);
}
void err_quit(const char* fmt, ...)
{
printf("%s\n",fmt);
exit(1);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err =pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err =pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
err =pthread_join(tid1, &tret);
if (err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread1 exit code %d\n", (int)tret);
err =pthread_join(tid2, &tret);
if (err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread2 exit code %d\n", (int)tret);
exit(0);
}
执行及输出结果如下:
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 exit code 2
从输出结果可以看出两个线程都正确的启动和退出了,但是只调用了第二个线程的清理处理程序,所以如果线程是通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意清理程序是按照与它们安装时相反的顺序被调用的。
线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源,对分离状态的线程进行pthread_join的调用可以产生失败,返回EINVAL。pthread_detach调用可以用于使线程进入分离状态。
#include<pthread.h>
intpthread_detach(pthread_t thread);
返回值:若成功则返回0,否则返回错误编号。
评论暂时关闭