连接线程

连接线程是指等待其终止,这可以视为条件变量的特定用法。

等待线程

使用 pthread_join 子例程会使一个线程等待另一个线程终止。 更加复杂的条件(例如,等待多个线程终止)能够通过使用条件变量实现。

调用 pthread_join 子例程

pthread_join 子例程阻塞正在调用的线程,直到指定的线程终止。 目标线程(等待终止的线程)不能拆离。 如果目标线程已经终止,但未拆离,那么 pthread_join 子例程立即返回。 在目标线程被连接之后,会自动将其拆离,并可回收其存储空间。

下表指出当线程调用 pthread_join 子例程时的可能情况,这取决于目标线程的 statedetachstate 属性。

目标状态 未拆离目标 已拆离目标
目标仍在运行 调用程序被阻塞,直到目标被终止。 调用立即返回,并指示有错误。
目标已终止 调用立即返回,并指示成功完成。  

多个连接

如果目标未被拆离,多个线程可连接同一个目标线程。 此操作是否成功取决于调用 pthread_join 子例程的顺序和目标线程终止的时刻。

  • 在目标线程的终止阻塞正在调用的线程之前,可能发生任何对 pthread_join 子例程的调用。
  • 目标线程终止时,所有被阻塞的线程均被唤醒,目标线程自动被拆离。
  • 发生在目标线程终止之后的任何对 pthread_join 子例程的调用将失败,因为线程被前面的连接拆离。
  • 如果在目标线程终止之前没有线程调用 pthread_join 子例程,那么第一次对 pthread_join 子例程的调用将立即返回,并指示成功完成,任何进一步的调用将失败。

连接示例

在下面的示例中,正好有五条消息以不同的语言显示时程序结束。 通过阻塞初始线程直到“writer”线程退出来完成。
#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */

void *Thread(void *string)
{
        int i;
 
        /* writes five messages and exits */
        for (i=0; i<5; i++)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}

int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_attr_t attr;
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;

        /* creates the right attribute */
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr,
                PTHREAD_CREATE_UNDETACHED);

        /* creates both threads */
        rc = pthread_create(&e_th, &attr, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, &attr, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_attr_destroy(&attr);

        /* joins the threads */
        pthread_join(e_th, NULL);
        pthread_join(f_th, NULL);
 
        pthread_exit(NULL);
}

线程不能连接自身,因为将发生死锁,且其会被库拆离。 但是,两个线程可能试着互相连接。 它们将死锁,但是这种情况不会被库拆离。

从线程返回信息

pthread_join 子例程还允许线程将信息返回给另一个线程。 当线程调用 pthread_exit 子例程或从其入口点例程返回时,它将返回一个指针 (请参阅 退出线程)。 将存储此指针,直到线程不被拆离,且该指针可被 pthread_join 子例程返回。

例如,多线程的 grep 命令可能选择以下示例中的实现。 在此示例中,初始线程为要扫描的每一个文件创建一个线程,每个线程具有相同的入口点例程。 然后初始线程等待所有的线程终止。 每个“扫描”线程将找到的行存储在动态分配的缓冲区中,并返回指向此缓冲区的指针。 初始线程会显示每个缓冲区并将其释放。
/* "scanning" thread */
...
buffer = malloc(...);
        /* finds the search pattern in the file 
           and stores the lines in the buffer   */
return (buffer);

/* initial thread */
...
for (/* each created thread */) {
        void *buf;
        pthread_join(thread, &buf);
        if (buf != NULL) {
                /* print all the lines in the buffer,
                        preceded by the filename of the thread */
                free(buf);
        }
}
...

如果目标线程被取消,pthread_join子程序会返回一个-1值,并将其转换为一个指针(请参阅取消线程)。 因为 -1 不可为指针值,将 -1 作为从线程返回的指针意味着线程被取消。

返回的指针可以指向任何类型的数据。 指针必须在线程被终止后仍然有效,且回收其存储空间。 因而,请避免返回值,因为在线程的存储空间被回收时会调用析构函数例程。

在返回指向动态分配给多个线程的存储空间的指针时,需要特别注意。 请注意下面的代码片段:
void *returned_data;
...
pthread_join(target_thread, &returned_data);
/* retrieves information from returned_data */
free(returned_data);
returned_data 指针在仅由一个线程执行时被释放。 如果多个线程并发执行上面的代码片段,returned_data 指针会被释放多次;这是必须避免的情况。 要避免这种情况,请使用互斥保护标记通知 returned_data 指针已被释放。 以下行来自前面的示例:
free(returned_data);
将被以下各行替换,其中互斥对象可用于锁定对关键区域的访问(假定 flag 变量已被初始化为 0):
/* lock - entering a critical region, no other thread should
                run this portion of code concurrently */
if (!flag) {
        free(returned_data);
        flag = 1;
}
/* unlock - exiting the critical region */

锁定对关键区域的访问可确保 returned_data 指针仅被释放一次。

返回指向动态分配给多个均执行不同代码的线程的存储空间的指针时,您必须确保恰好仅有一个线程释放指针。