Posix 标准定义了许多线程系统调用。
在同一进程中创建新线程的 posix 函数具有以下函数原型:
#include
int pthread_create(pthread_t *thread, const pthread_attr_t
*attr, void *(*start_routine, void*),void *arg);
该系统调用有四个参数,第一个 *thread 是指向线程 ID 号的指针(pthread_t 类型是 int),调用程序后续需要该值来进行线程同步。
数据类型 pthread_attr_t 允许调用程序设置线程的一些属性,例如堆栈的大小,默认设置为NULL即可。
第三个参数 start_routine 是新创建的线程将启动的函数的名称。 该函数必须具有以下原型:void *start_routine(void *arg)
,(start_routine 替换为实际使用的函数名称),该函数要使用一个参数,最后一个参数就是指向该参数的指针。
通常情况下,如果 pthread_create 调用成功创建新线程,则返回零;如果失败,则返回负值;如果失败,外部全局变量 errno 将被设置为指示失败原因的值。
所有线程都同时开始运行,所以无法保证哪个线程将首先运行,在竞争条件下,两个或多个事件几乎同时发生,并且不能保证一个事件会在另一个事件之前发生,事件的顺序是不确定的。
有一种可能性是主调用线程可以在所有线程终止之前终止。 由于所有线程都在同一个进程空间中运行,因此当主线程终止时,整个进程就会死亡。 而且,任何线程如果调用exit(),整个进程都会立即终止,这意味着某些线程可能在完成其工作之前就终止了。
还有另外两个 posix 线程系统调用可以帮助解决这个问题。 要终止单个线程而不终止整个进程,请使用系统调用
void pthread_exit(void *value_ptr);
参数是指向返回值的指针,可以将其设置为 NULL。
调用线程可以等待特定线程随着调用而终止:
int pthread_join(pthread_t thread, void **value_ptr);
第一个参数是进程正在等待的线程 ID;
第二个参数是指向线程传回的参数的指针,可以将其设置为 NULL。
该函数将阻塞,直到线程终止。 请注意该函数与 wait 函数在功能上的相似之处,后者等待子进程死亡。
这是一些非常简单的线程代码:
/* Alert: link to the pthreads library by appending
-lpthread to the compile statement */
#include
#include
#include
#include /* for sleep */
#include /* for malloc */
#define NUM_THREADS 5
void *sleeping(void *); /* thread routine */
pthread_t tid[NUM_THREADS]; /* array of thread IDs */
int main()
{
int *sleeptime, i, retval;
for ( i = 0; i < NUM_THREADS; i++) {
sleeptime = (int *)malloc(sizeof(int));
*sleeptime = (i+1)*2;
retval =pthread_create(&tid[i], NULL, sleeping,
(void *)sleeptime);
if (retval != 0) {
perror("Error, could not create thread");
}
}
for ( i = 0; i < NUM_THREADS; i++)
pthread_join(tid[i], NULL);
printf("main() reporting that all %d threads have terminated\n", i);
return (0);
} /* main */
void *sleeping(void *arg)
{
int sleep_time = *(int *)arg;
printf("thread %d sleeping %d seconds ...\n",
pthread_self(), sleep_time);
sleep(sleep_time);
printf("\nthread %d awakening\n", pthread_self());
return (NULL);
}
将参数传递给线程例程需要特别注意,参数是一个指针。
重要的是,通常每个线程的参数指向不同的内存,这就是为什么调用线程的循环每次都使用 malloc 分配新内存。
另外,请确保调用函数在创建线程后不会更改 arg 指向的内存值。 因为调用函数和新创建的线程之间存在竞争条件,所以我们不知道线程还是调用函数会先运行。
如果您以明显(但错误)的方式编写循环,如下所示:
for (i=0;i<5;i++) {
retval = pthread_create(&threadIds[i], NULL,
ThreadRoutine, &i);
...
这段程序也会运行,而不会出现错误,但所有五个线程的参数可能都是相同的,而不是每个线程都不同,因为您每次传递相同的地址。
使用 pthread_exit() 从将死的线程返回一个值会考验开发者的指针技巧,该函数的参数是一个 void 指针,void 指针可以指向任何数据类型。
函数 pthread_join 的第二个参数是一个指向 void 的指针,它也可以指向任何特定的数据类型(大概与 pthread_exit() 的参数类型相同。当对 pthread_join 的调用返回时,第二个参数将指向 为该线程的 pthread_exit 返回的参数。
这是一个制作文件的副本简短的程序。
/* A program that copies files in parallel
using pthreads */
/* Alert: link to the pthreads library by appending
-lpthread to the compile statement */
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* pthread_t copy_tid;*/
extern int errno;
#define BUFSIZE 256
void *copy_file(void *arg)
{
int infile, outfile;
int bytes_read = 0;
int bytes_written = 0;
char buffer[BUFSIZE];
char outfilename[128];
int *ret;
ret = (int *)malloc(sizeof(int));
*ret = 0;
infile = open(arg,O_RDONLY);
if (infile < 0) {
fprintf(stderr,"Error opening file %s: ",
(char *)arg);
fprintf(stderr,"%s\n",strerror(errno));
pthread_exit(ret);
}
strcpy(outfilename,"Copy_Of_");
strcat(outfilename,arg);
outfile = open(outfilename,O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (outfile < 0) {
fprintf(stderr,"Error opening file %s",outfilename);
fprintf(stderr,"%s\n",strerror(errno));
pthread_exit(ret);
}
while (1) {
bytes_read = read(infile, buffer, BUFSIZE);
if (bytes_read == 0)
break;
else if (bytes_read < 0) {
perror("reading");
pthread_exit(ret);
}
bytes_written = write(outfile, buffer, bytes_read);
if (bytes_written != bytes_read) {
perror("writing");
pthread_exit(ret);
}
*ret += bytes_written;
}
close(infile);
close(outfile);
pthread_exit(ret);
return ret; /* we never get here, but the compiler
likes to see this line */
}
int main(int argc, char *argv[])
{
pthread_t tid[10]; /* max of 10 possible threads */
int total_bytes_copied = 0;
int *bytes_copied_p;
int i;
int ret;
for (i=1;i < argc;i++) {
ret = pthread_create(&tid[i], NULL, copy_file,
(void *)argv[i]);
if (ret != 0) {
fprintf(stderr,"Could not create thread %d: %s\n",
i, strerror(errno));
fprintf(stderr,"ret is %d\n",ret);
}
}
/* wait for copies to complete */
for (i=1;i < argc;i++) {
ret = pthread_join(tid[i],(void **)&(bytes_copied_p));
if (ret != 0) {
fprintf(stderr,"No thread %d to join: %s\n",
i, strerror(errno));
}
else {
printf("Thread %d copied %d bytes from %s\n",
i, *bytes_copied_p, argv[i]);
total_bytes_copied += *bytes_copied_p;
}
}
printf("Total bytes copied = %d\n",total_bytes_copied);
return 0;
}
请特别注意 pthread_exit 将值(本例为整数)传递回主线程的方式; 并注意主线程如何使用 pthread_join 的第二个参数来访问该值。
您可能会发现这个语法 (void ** )&(bytes_copied_p) 有点怪,变量 bytes_copied_p 是一个指向整数的指针。 通过在其前面加上一个 amersand (&),我们将其地址传递给该函数。 这将允许函数更改它所指向的内容,这是一件好事,因为当您调用该函数时,它并不指向任何地方。 该函数更改其值,使其指向 thread_exit 的参数。 (void **) 将此值转换为指向指针的指针,符合编译器要求。
使用线程的程序的典型设计是有一个主线程,该主线程创建一定数量的子线程,然后等待它们终止,上面的两个例子都采用了这种设计。 但实际上,任何线程都可以创建新线程,并且父线程不必等待子线程终止。
如果一个线程想要返回多个值,您可以创建一个包含所有返回值的结构,并返回一个指向该结构实例的指针。 请看这段代码:
/* On some systems you will need to link to the pthread library by
appending -l pthread to the compile line */
#include
#include
#include
struct thedata {
int x;
char s[32];
};
void *threadroutine(void *arg)
{
struct thedata *t = (struct thedata *)malloc(sizeof(struct thedata));
t->x = 17;
strcpy(t->s,"Hello world!");
pthread_exit(t);
}
int main()
{
struct thedata *q;
pthread_t n;
int retval;
retval = pthread_create(&n,NULL,threadroutine,NULL);
if (retval < 0) {
printf("error, could not create thread\n");
exit(0);
}
/***************************************************
write code here to get the return value from the
thread and display the results. Make sure that
your code does routine error checking
*****************************************************/
}