pthread 学习笔记 (1)

pthread 是 POSIX Thread 的缩写,是一套标准线程库,大部分的 *nix 系统都实现了它,在 windows 上也有相应的实现。这里的实验环境为 debian 6.0。

并行和并发(参考资料 [1])

并行的英文是 parallelism,并发的英文是 concurrency,两者表达的意思并不一样:并行是指多条指令 同时 执行(这里的“同时”指时间上的同时),而并发是指多条指令可能以任意顺序执行(也有可能是并行执行)。

第一个 pthread 程序

#include <stdio.h>
#include <pthread.h>

static void* print_tid(void* arg)
{
   printf("thread id: %d\n", *((int*)arg));

   return arg;
}

#define THREAD_NR 10

int main()
{
   int status;
   int i, tid[THREAD_NR];
   pthread_t pt[THREAD_NR];

   for (i = 0; i < THREAD_NR; ++i) {
      tid[i] = i;
      status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
      if (status != 0)
         fprintf(stderr, "create thread %d fail.\n", i);
   }

   return 0;
}

使用 gcc 编译的时候要加上“-lpthread”。

pthread_create() 函数创建一个线程。manpage 中,pthread_create() 的函数原型为:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
      void *(*start_routine) (void *), void *arg);

第一个参数是线程的唯一标识;第二个参数设置线程的属性,如果为 NULL 则使用默认的属性;第三个参数是一个函数指针,指向线程要执行的回调函数;第四个参数是传递给回调函数的参数。

运行几次这个程序,从输出结果看到,线程打印出了一些值,这些值的顺序是乱序的,说明操作系统并一定按照线程创建的顺序执行线程;同时也看到有些线程好像并没有被执行,有些线程打印的值并不是传递进去的值,这是因为创建完所有线程后 main() 函数就退出了,这意味着主线程结束,而由主线程创建的线程也会随之结束,尽管其中有些线程还没来得及运行。

如果想让主线程等到所有线程都运行完后再退出,可以使用 pthread_join()。pthread_join() 等待线程结束并且回收线程的资源:

int pthread_join(pthread_t thread, void **retval);

第一个参数是由 pthread_create() 创建线程时分配的 thread id,第二个参数是一个二级指针,如果 retval 为空则忽略回调函数的返回值,否则回调函数的返回值保存在 *retval 中:

#include <stdio.h>
#include <pthread.h>

static void* print_tid(void* arg)
{
   printf("thread id: %d\n", *((int*)arg));

   return arg;
}

#define THREAD_NR 10

int main()
{
   int status;
   int i, tid[THREAD_NR];
   pthread_t pt[THREAD_NR];

   for (i = 0; i < THREAD_NR; ++i) {
      tid[i] = i;
      status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
      if (status != 0)
         fprintf(stderr, "create thread %d fail.\n", i);
   }

   for (i = 0; i < THREAD_NR; ++i) {
      status = pthread_join(pt[i], NULL);
      if (status != 0)
         fprintf(stderr, "join thread %d fail.\n", i);
   }

   return 0;
}

如果仅仅是需要执行线程而不关心线程的返回值,可以使用 pthread_detach():

int pthread_detach(pthread_t thread);

这样操作系统在线程执行完后会回收线程的资源,线程的返回值,状态等信息都被忽略。但是这样又会出现第一个程序的问题,就是如果主线程结束了还没来得及执行的线程也会被杀死,这就需要另外的方法来保证主线程等到所有线程都执行完后再结束。

对同一个线程只能调用 pthread_join() 一次,并且不能调用了 pthread_detach() 后又调用 pthread_join(),否则可能会出现不可预料的错误。

回调函数的返回值

把上面的程序稍加改动:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static void* print_tid(void* arg)
{
   int* t = malloc(sizeof(int));

   *t = *((int*)arg) + 5;
   printf("thread id: %d, return addr: %p\n", *((int*)arg), t);

   return t;
}

#define THREAD_NR 10

int main()
{
   int status;
   int i, tid[THREAD_NR], *ret[THREAD_NR];
   pthread_t pt[THREAD_NR];

   for (i = 0; i < THREAD_NR; ++i) {
      tid[i] = i;
      status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
      if (status != 0)
         fprintf(stderr, "create thread %d fail.\n", i);
   }

   for (i = 0; i < THREAD_NR; ++i) {
      status = pthread_join(pt[i], (void**)(&ret[i]));
      if (status != 0)
         fprintf(stderr, "join thread %d fail.\n", i);
   }

   for (i = 0; i < THREAD_NR; ++i) {
      printf("thread %d return %d, return addr: %p\n", i, *ret[i], ret[i]);
      free(ret[i]);
   }

   return 0;
}

在回调函数中使用 malloc() 为返回值分配空间,调用 pthread_join() 时加上保存返回值的指针地址,最后的打印结果表明回调函数的返回值的确保存在 ret 数组中。

要结束线程并返回值除了使用 return 语句外,还可以使用函数 pthread_exit():

void pthread_exit(void *retval);

pthread_exit() 函数结束当前的线程并把传递给它的值保存在 pthread_join() 函数第二个参数中,并且会调用一些由 pthread_cleanup_push() 注册的函数(目前还不懂……)。在上面程序的 print_tid() 函数中,最后的 return 语句可以使用下面的语句代替:

pthread_exit(t);

pthread_exit() 不同于 exit(),前者只会结束当前线程,而后者则会结束整个进程。

参考资料

[1] Multithreaded Programming (POSIX pthreads Tutorial)
[2] Michael Kerrisk. The Linux Programming Interface.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注