可连接和分离的线程 | Joinable and Detached Threads

本文是对《Joinable And Detached Threads -- by Loïc》的简体中文的翻译版本。

介绍

默认情况下,创建的线程即是可连接的(或可结合的,joinable)。这意味着我们可以使用pthread_join()函数在任何其它线程中等待它(可连接线程)的终止:

#include <pthread.h>
int pthread_join(
    pthread_t thread,  //thread to join
    void **value_ptr   //store value returned by thread
);

这个函数将阻塞调用线程,直到目标线程thread终止。当value_ptr非空时,其中将包含着由thread线程返回的信息。在我们的文章POSIX线程参数传递 | Pthreads argument passing中看到了如何使用pthread_join()函数。

可连接的线程及其结果

一个线程在我们连接(join)它之前可能就已经终止了。结果是,当一个线程是可连接时,POSIX线程系统必须保持、维护某些信息:至少是该线程的ID和返回值(the returned value)[1]。的确,正在等待连接该线程的线程是需要这些信息的。
实际上,POSIX线程系统也可保持和可连接线程相关的其它资源信息,比如线程的栈。从POSIX的角度来看,这是完全合法的。事实上,pthread_join()保证回收与已连接线程的任何存储空间。

Pthreads的内部实现

维护整个线程的栈可能仅仅几个字节被确切需要,这看起来像是对系统资源的极大浪费(在一个64位的架构上,线程ID和返回值信息加起来一般也只需要16个字节)。然而,一些Pthreads实现使用这种可能性来简化和优化线程管理。实际上,线程实现需要存储和管理每个线程特有的属性,比如:

  • 线程ID,线程属性,起始入口函数,参数及返回值;
  • 线程的调度策略和优先级;
  • 信号掩码,备用信号栈;
  • 用于取消、清理缓存的标志;
  • 用于线程私有数据的键;
  • 错误编号;
  • ... ...

让我们称线程的这种管理存储为线程控制块(the Thread Control Block, TCB)。一种常见的技术是一次性分配堆栈和TCB(比如使用单次mmap()调用),并把TCB放在栈的开始位置处[2]

在这样类似的架构上,我们不能在没有回收TCB的情况下回收整个栈。这就是问题的关键所在:对于连接操作所需要的信息是TCB的一部分,因此整个栈空间都需要被维持。众所周知,在Linux、AIX、Sloaris等的Pthreads的实现中都采用这种技术。

分离线程

那当我的线程不返回任何有用的信息,并且我不必等待它的完成时,我该怎么办?仅仅为了清理的意图,无论如何还是得必须调用pthrad_join()吗?
幸运的是,不必这样做。Pthreads提供了一种机制告诉管理系统:我正在开启这个线程,但我对连接它并没有兴趣;一旦该线程结束,请为我执行清理动作。这个操作叫做分离一个线程。我们可以按照如下方法分离一个线程:

  • 在线程的创建期间,采用detachstate线程属性
  • 在任何线程处调用pthread_detach()函数

让我们先看一下第二种形式,也是最简单的:

#include <pthread.h>
int pthread_detach(
    pthread_t thread  //thread to detach
);

函数pthread_detach()可以被任何线程调用,特别是从线程内分离(即自家线程调用,通过pthread_self() API可以获得它自己的线程ID)。
要想以分离状态创建一个线程,我们可以通过pthread_attr_setdetachstate()函数设置detachstate线程属性为PTHREAD_CREATE_DETACHED。如下所示:

#include <pthread.h>
#
pthread_t      tid;  // thread ID
pthread_attr_t attr; // thread attribute
#
// set thread detachstate attribute to DETACHED
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#
// create the thread 
pthread_create(&tid, &attr, start_routine, arg);
...

下述程序阐明了分离一个线程的两种方法。

/*------------------------------- join_01.c --------------------------------*
On Linux, compile with: 
cc -std=c99 -pthread join_01.c -o join_01 
 #
Check your system documentation how to enable C99 and POSIX threads on 
other Un*x systems.
 #
Copyright Loic Domaigne. 
Licensed under the Apache License, Version 2.0.
*--------------------------------------------------------------------------*/
 #
#include <unistd.h>  // sleep()
#include <pthread.h> 
#include <stdio.h>   
#include <stdlib.h>  // EXIT_SUCCESS
#include <string.h>  // strerror() 
#include <errno.h>
 #
/***************************************************************************/
/* our macro for errors checking                                           */
/***************************************************************************/
#define COND_CHECK(func, cond, retv, errv) \
if ( (cond) ) \
{ \
   fprintf(stderr, "\n[CHECK FAILED at %s:%d]\n| %s(...)=%d (%s)\n\n",\
              __FILE__,__LINE__,func,retv,strerror(errv)); \
   exit(EXIT_FAILURE); \
}
 #
#define ErrnoCheck(func,cond,retv)  COND_CHECK(func, cond, retv, errno)
#define PthreadCheck(func,rc) COND_CHECK(func,(rc!=0), rc, rc)
 #
/*****************************************************************************/
/* thread- dummy thread                                                      */
/*****************************************************************************/
void*
thread(void* ignore)
{
   sleep(1);
   return NULL;
}
 #
/*****************************************************************************/
/* detach_state. Print detachstate of a thread.                              */
/*****************************************************************************/
/* 
 * We find out indirectly if a thread is detached using pthread_join().  
 * If a thread is detached, then pthread_join() fails with EINVAL. 
 * Otherwise the thread is joined, and hence was joinable. 
 *
 */
void
detach_state(
   pthread_t   tid,  // thread to check detach status
   const char *tname // thread name
   )
{
   int rc; // return code
 #
   rc = pthread_join(tid, NULL);
   if ( rc==EINVAL ) 
   {
      printf("%s is detached\n", tname);
   }
   else if ( rc==0 )  
   {  
      printf("%s was joinable\n", tname);
   }
   else 
   {
      printf("%s: pthread_join() = %d (%s)\n", 
             tname, rc, strerror(rc)
            );
   }
}
 #
/*****************************************************************************/
/* main- main thread                                                         */
/*****************************************************************************/
int
main()
{
   pthread_t tid1, tid2, tid3; // thread 1,2 and 3.
   pthread_attr_t attr;        // thread's attribute
   int rc;  // return code
 #
   /*--------------------------------------------------------*/
   /* 1st test: normal thread creation                       */
   /*--------------------------------------------------------*/
   rc = pthread_create(&tid1, NULL, thread, NULL);
   PthreadCheck("pthread_create", rc);
   detach_state(tid1, "thread1"); // expect: joinable 
 #
   /*--------------------------------------------------------*/
   /* 2nd test: detach thread from main thread               */
   /*--------------------------------------------------------*/
   rc = pthread_create(&tid2, NULL, thread, NULL);
   PthreadCheck("pthread_create", rc);
   rc = pthread_detach(tid2);
   PthreadCheck("pthread_detach", rc);
   detach_state(tid2, "thread2"); // expect: detached
 #
   /*--------------------------------------------------------*/
   /* 3rd test: create detached thread                       */
   /*--------------------------------------------------------*/
 #
   // set detachstate attribute to DETACHED
   //
   rc=pthread_attr_init(&attr);
   PthreadCheck("pthread_attr_init", rc);
   rc=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
   PthreadCheck("pthread_attr_setdetachstate", rc);
 #
   // create thread now 
   //
   rc = pthread_create(&tid3, &attr, thread, NULL);
   PthreadCheck("pthread_create", rc);
   detach_state(tid3, "thread3");
 #
   /*--------------------------------------------------------*/
   /* that's all folks!                                      */
   /*--------------------------------------------------------*/
   return EXIT_SUCCESS;
}

对于连接或分离最重要的规则就是:

  • 不要连接一个已经被连接的线程;(注:已连接的线程栈空间已被收回,再次连接将得不到可连接线程的信息)
  • 不要连接一个分离线程;(注:连接操作只可用于可连接的线程,因为分离线程栈空间的收回是由系统内部来做的)
  • 如果你分离一个线程,你不能“重连接”它;(注:调用pthread_detach()后,可连接线程的栈空间已被收回,无法再恢复)

Notes and further Reading

  • [1] pthread & detach. A post on c.p.t. where Patrick TJ MacPhee explains that (in theory) only the thread ID and returned value should be retained.
  • [2] pthread_join and thread stack. A post on c.p.t. where Paul Pluzhnikov explains why the stack is held until the thread is joined.
  • The main thread. Article where we discuss the special semantic of the main thread.
  • Pthreads argument passing. Article where we discuss how to pass arguments between threads.
  • David R. Butenhof: Programming with POSIX Threads, Addison-Wesley, ISBN-13 978-0-201-63392-4. See in particular Section 2.1, pp 35-39 and section 5.2.3, pp 138-141.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容