创建线程:
- 最初,main函数包含了一个缺省的线程。其它线程则需要程序员显式地创建。
- pthread_create创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。
- pthread_create参数:
- thread:返回一个不透明的,唯一的新线程标识符。
- attr:不透明的线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值。
- start_routine:线程将会执行一次的C函数。
- arg:传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。
- 一个进程可以创建的线程最大数量取决于系统实现。
- 一旦创建,线程就称为peers,可以创建其它线程。线程之间没有指定的结构和依赖关系。
|
| Q:一个线程被创建后,怎么知道操作系统何时调度该线程使之运行? A:除非使用了Pthreads的调度机制,否则线程何时何地被执行取决于操作系统的实现。强壮的程序应该不依赖于线程执行的顺序。 |
线程属性:
- 线程被创建时会带有默认的属性。其中的一些属性可以被程序员用线程属性对象来修改。
- pthread_attr_init和pthread_attr_destroy用于初始化/销毁线程属性对象。
- 其它的一些函数用于查询和设置线程属性对象的指定属性。
- 一些属性下面将会讨论。
结束终止:
- 结束线程的方法有一下几种:
- 线程从主线程(main函数的初始线程)返回。
- 线程调用了pthread_exit函数。
- 其它线程使用pthread_cancel函数结束线程。
- 调用exec或者exit函数,整个进程结束。
- pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作时,不在需要时候被调用,退出线程。
- 如果main()在其他线程创建前用pthread_exit()退出了,其他线程将会继续执行。否则,他们会随着main的结束而终止。
- 程序员可以可选择的指定终止状态,当任何线程连接(join)该线程时,该状态就返回给连接(join)该线程的线程。
- 清理:pthread_exit()函数并不会关闭文件,任何在线程中打开的文件将会一直处于打开状态,直到线程结束。
- 讨论:对于正常退出,可以免于调用pthread_exit()。当然,除非你想返回一个返回值。然而,在main中,有一个问题,就是当main结束时,其它线程还没有被创建。如果此时没有显式的调用pthread_exit(),当main结束时,进程(和所有线程)都会终止。可以在main中调用pthread_exit(),此时尽管在main中已经没有可执行的代码了,进程和所有线程将保持存活状态,。
Example Code - Pthread Creation and Termination
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
int tid;
tid = (int)threadid;
printf("Hello World! It's me, thread #%d!\n", tid);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc, t;
for(t=0; t<NUM_THREADS; t++){
printf("In main: creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
- pthread_create()函数允许程序员向线程的start routine传递一个参数。当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create()传递一个指向该结构体的指针,来打破传递参数的个数的限制。
- 所有参数都应该传引用传递并转化成(void*)。
|
| Q:怎样安全地向一个新创建的线程传递数据? A:确保所传递的数据是线程安全的(不能被其他线程修改)。下面三个例子演示了那个应该和那个不应该。 Example 1 - Thread Argument Passing 下面的代码片段演示了如何向一个线程传递一个简单的整数。主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。 int *taskids[NUM_THREADS];
for(t=0; t<NUM_THREADS; t++) { taskids[t] = (int *) malloc(sizeof(int)); *taskids[t] = t; printf("Creating thread %d\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]); ... }
|
在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。
[cpp]
int pthread_join(pthread_t tid, void**thread_return);
若成功则返回0,若出错则为非零。
线程通过调用pthread_join函数等待其他线程终止。调用pthread_join函数线程会阻塞,直到线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源。
[cpp] view plaincopyprint?int pthread_detach(pthread_t tid);
若成功则返回0,若出错则为非零。
pthread_detach用于分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。 由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码 pthread_detach(pthread_self())或者父线程调用 pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。