Linux编程学习笔记 | Linux多线程学习[1] - 线程的创建和基本控制

文章系列原因

2017年年初,我给自己定了一个小小的目标:学习Linux编程,并通过网络来分享自己的学习心得。为了完成这个小小的目标,我开始用通过写文章来记录我的学习心得,希望在年底时,我能完成24篇Linux相关的学习文档,以实现我这个小小的目标。这是这个系列的第一篇文章,是我对最近学习Linux多线程的总结。

什么是线程

我们来看看维基百科是如何对线程进行定义的:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

一个进程可以包含多个线程,这些线程有各自的调用栈(call stack),自己的寄存器环境(register context)以及自己的线程本地存储(thread-local storage)。每条线程都是并发执行不同的任务。因为还没写进程相关的文章,这里我就不进一步说明进程和线程的区别了,我会在进程或者单独的文章中说明它们的异同。

线程的生命周期

线程的四个状态

线程在其生命周期中有四种状态,分别是就绪、运行、阻塞和终止,下面这个表格解释了这四种状态:

状态 含义
就绪 线程能够运行,但是在等待可用的处理器
运行 线程正在运行,在多核系统中,可能同时有多个线程在运行
阻塞 线程在等待处理器以外的其他条件
终止 线程从启动函数中返回,或者调用pthread_exit函数,或者被取消

线程可以在一定条件下转换其状态,下图显示了线程是如何转换其状态的:

线程状态的改变

线程的创建

要创建线程,我们使用 pthread_create() 这个函数,函数说明如下:

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

args:
    pthread_t *thread              : 指向新线程的线程号的指针,如果创建成功则将线程号写回thread指向的内存空间 
    const pthread_attr_t *attr     : 指向新线程属性的指针
    void *(*start_routine) (void *): 新线程要执行函数的地址(回调函数)
    void *arg                      : 指向新线程要执行函数的参数的指针

return: 
    线程创建的状态,0是成功,非0失败

每一个成功创建的线程都有一个线程号,我们可以通过 pthread_self() 这个函数获取调用这个函数的线程的线程号。有一点我们需要注意,如果需要编译 pthread_XX() 相关的函数,我们需要在编译时加入 -pthread

线程的基本控制

线程的结束

要结束一个线程,我们通常有以下几种方法:

方法 说明
exit() 终止整个进程,并释放整个进程的内存空间
pthread_exit() 终止线程,但不释放非分离线程的内存空间
pthread_cancel() 其他线程通过信号取消当前线程

exit() 会终止整个进程,因此我们几乎不会使用它来结束线程。我们常用 pthread_cancel()pthread_exit() 函数来结束线程。这里,我们重点看看 pthread_exit() 这个函数:

void pthread_exit(void *retval);

args: 
    void *retval: 指向线程结束码的指针

return:
    无

要结束一个线程,只需要在线程调用的函数中加入 pthread_exit(X) 即可,但有一点需要特别注意:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间。这会导致该线程变成了“僵尸线程”。“僵尸线程”会占用大量的系统资源,因此我们要避免“僵尸线程”的出现。

线程的连接

在上一部分我们提到了 pthread_join() 这个函数,在这一节,我们先来看看这个函数:

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

args:
    pthread_t thread: 被连接线程的线程号
    void **retval   : 指向一个指向被连接线程的返回码的指针的指针
    
return:
    线程连接的状态,0是成功,非0是失败

当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:

  1. 被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
  2. 一个线程只能被一个线程所连接。
  3. 被连接的线程必须是非分离的,否则连接会出错。

线程的分离

在上一节中,我们在谈 pthread_join() 时说到了只有非分离的线程才能使用 pthread_join(),这节我们就来看看什么是线程的分离。在Linux中,一个线程要么是可连接的,要么是可分离的。当我们创建一个线程的时候,线程默认是可连接的。可连接和可分离的线程有以下的区别:

线程类型 说明
可连接的线程 能够被其他线程回收或杀死,在其被杀死前,内存空间不会自动被释放
可分离的线程 不能被其他线程回收或杀死,其内存空间在它终止时由系统自动释放

我们可以看到,对于可连接的线程而而言,它不会自动释放其内存空间。因此对于这类线程,我们必须要配合使用 pthread_join() 函数。而对于可分离的函数,我们就不能使用 pthread_join() 函数。
要使线程分离,我们有两种方法:1)通过修改线程属性让其成为可分离线程;2)通过调用函数 pthread_detach() 使新的线程成为可分离线程。
下面是 pthread_detach() 函数的说明:

int pthread_detach(pthread_t thread);

args:
    pthread_t thread: 需要分离线程的线程号

return:
    线程分离的状态,0是成功,非0是失败

我们只需要提供需要分离线程的线程号,便可以使其由可连接的线程变为可分离的线程。

主线程

在上面的几节中,我们讲了线程的创建,结束和连接。这节我们来看一个特殊的线程 - 主线程。
在C程序中,main(int argc, char **argv) 就是一个主线程。我们可以在主线程中做任何普通线程可以做的事情,但它和一般的线程有有一个很大的区别:主线程返回或者运行结束时会导致进程的结束,而进程的结束会导致进程中所有线程的结束。为了不让主线程结束所有的线程,根据我们之前所学的知识,有这么几个解决办法:

  1. 不让主线程返回或者结束(在 return 前加入 while(1) 语句)。
  2. 调用 pthread_exit() 来结束主线程,当主线程 return 后,其内存空间会被释放。
  3. return 前调用 pthread_join(),这时主线程将被阻塞,直到被连接的线程执行结束后,才接着运行。

在这三种方法中,前两种很少被使用,第三种是常用的方法。

总结

这篇文章主要介绍了线程的基本概念,线程的生命周期和状态,线程的创建、结束、连接、分离以及主线程。在下一篇中,我将介绍多线程中的重点 - 同步。

如果觉得本文对你有帮助,请多多点赞支持,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容