三十六、Linux 线程——线程基本概念及线程的创建和终止
36.1 线程介绍
36.1.1 线程的基本概念
- 进程是资源管理的最小单位,线程是程序执行的最小单位
- 每个进程都有自己的数据段、代码段和堆栈段。
- 线程通常叫做轻型的进程,它包含独立的栈和 CPU 寄存器状态,线程是进程的一条执行路径,每个线程共享其所附属进程的所有资源,包括打开的文件、内存页面、信号标识及动态分配的内存等。
- 因为线程和进程比起来很小,所以相对来说,线程花费更少的 CPU 资源
- 在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持多处理器,并且减少进程上下文切换的开销。
36.1.2 进程和线程的关系
- 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一用户内存空间,当进程退出时,该进程所产生的线程都会被强制退出并清除。
- 一个进程至少需要一个线程作为它的指令执行,进程管理着资源(比如 CPU、内存、文件等等),并将线程分配到某个 CPU 上执行

36.1.3 线程分类
- 线程按照其调度者可分为用户级线程和内核级线程两种:
- 用户级线程:主要解决的是上下文切换的问题,其调度过程由用户决定
- 内核级线程:由内核调度机制实现
- 现在大多数操作系统都采用用户级线程和内核级线程并存的方法
- 用户级线程要绑定内核级线程运行,一个进程中的内核级线程会分配到固定的时间片,用户级线程分配的时间片以内核级线程为准
- 默认情况下,用户级线程和内核级线程是一对一,也可以多对一,这样实时性就会比较差
- 当 CPU 分配给线程的时间片用完后但线程没有执行完毕,此时线程会从运行状态返回到就绪状态,将 CPU 让给其他线程使用
36.1.4 Linux 线程实现
- 以下线程均为用户级线程
- 在Linux 中,一般采用 pthread 线程库实现线程的访问与控制,由 POSIX 提出,具有良好的可移植性
- Linux 线程程序编译需要在 gcc 上链接库 pthread
36.1.5 线程标识
- 每个进程内部的不同线程都由自己的唯一标识(ID)
- 线程标识只在它所属的进程环境中有效
- 线程标识是 pthread_t 数据类型
#include <pthread.h>
int pthread_equal(pthread_t, pthread_t);
- 函数功能:判断两个线程是否相等
- 返回值:相等返回非0;否则返回0
#include <pthread.h>
pthread_t pthread_self(void);
- 函数功能:获取当前线程的线程 ID
- 返回值:调用线程的线程 ID
36.2 线程的创建和销毁
36.2.1 线程创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),
5 void *restrict arg);
- 函数功能:创建一个线程
- 函数参数:
- tidp:线程标识符指针
- attr:线程属性指针
- start_rtn:线程运行函数的起始地址
- arg:传递给线程运行函数的参数
- 返回值:成功,返回0;失败,返回错误编号
- 新创建线程从 start_trn 函数的地址开始运行
- 不能保证新线程和调用线程的执行顺序
36.2.2 线程终止
- 主动终止:
- 线程的执行函数中调用 return 语句
- 调用 pthread_exit()
- 被动终止:
- 线程可以被同一进程的其他线程取消,其他线程调用 pthread_cancel(pthid)
#include <pthread.h>
int pthread_cancel(pthread_t tid);
void pthread_exit(void *retval);
int pthread_join(pthread_t th, void **thread_return);
- pthread_cancel:线程可以别同一进程的其他线程取消,tid 为被终止的线程标识符
- pthread_exit:
- retval:pthread_exit 调用者线程的返回值,可由其他函数和 pthread_join 来检测获取
- 线程退出时,使用函数 pthread_exit,是线程的主动行为
- 由于一个进程中的多个线程共享数据段,因此通常在线程退出后,退出线程所占用的资源并不会随线程结束而释放。所以需要 pthread_join 函数来等待线程结束,类似于 wait 系统调用
- pthread_join
- th:被等待线程的标识符
- thread_return:用户定义指针,用来存储被等待线程的返回值
36.3 例子
36.3.1 龟兔赛跑
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h> typedef struct {
char name[];
int time;
int start;
int end;
}RaceArg; /** 定义线程运行函数 */
void *th_fn(void *arg)
{
RaceArg *r = (RaceArg *)arg;
int i = r->start; for(; i <= r->end; i++){
printf("%s(%lx) running %d\n", r->name, pthread_self(), i);
usleep(r->time);
} return (void *);
} int main(void)
{
int err;
pthread_t rabbit, turtle; ///< 定义线程标识符
RaceArg r_a = {"rabbit", (int )(drand48() * 100000000), 20, 50};
RaceArg t_a = {"turtle", (int )(drand48() * ), , }; /** 创建 rabbit 线程 */
if((err = pthread_create(&rabbit, NULL, th_fn, (void *)&r_a)) != ){
perror("pthread_create error");
} /** 创建 turtle 线程 */
if((err = pthread_create(&turtle, NULL, th_fn, (void *)&t_a)) != ){
perror("pthread_create error");
} //sleep(10);
/** 主控线程调用 pthread_join(), 自己会阻塞,直到 rabbit 线程结束方可运行 */
pthread_join(rabbit, NULL);
pthread_join(turtle, NULL);
printf("control thread id: %lx\n", pthread_self());
printf("finisheld!\n");
return ;
}
运行结果如下:

可以看到每次都只有一个线程在执行。
在进程的线程中,每个线程的变量所在地方如下:

36.3.2 获取线程终止返回值
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h> typedef struct {
int d1;
int d2;
}Arg; void *th_fn(void *arg)
{
Arg *r = (Arg *)arg; /* 获取普通变量值
return (void *)(r->d1 + r->d2);
*/ /** 获取结构体对象 */
return r;
} int main(void)
{
int err;
pthread_t th;
Arg r = {, }; if((err = pthread_create(&th, NULL, th_fn, (void *)&r)) != ){
perror("pthread_create error");
} /** 获取普通变量值 */
/* 第一种获取返回值的方法
int *result; pthread_join(th, (void **)&result);
printf("result is %d\n", (int)result);
*/ /** 第二种获取返回值的方法 */
/*
int result;
pthread_join(th, (void *)&result);
printf("result is %d\n", result);
*/ /** 获取结构体变量 */
/*
int *result;
pthread_join(th, (void **)&result);
printf("result is %d\n",((Arg *)result)->d1 + ((Arg *)result)->d2);
*/ int result;
pthread_join(th, (void *)&result);
printf("result is %d\n",((Arg *)result)->d1 + ((Arg *)result)->d2); return ;
}
36.3.3 龟兔赛跑获取返回值
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h> typedef struct {
char name[];
int time;
int start;
int end;
}RaceArg; /** 定义线程运行函数 */
void *th_fn(void *arg)
{
RaceArg *r = (RaceArg *)arg;
int i = r->start; for(; i <= r->end; i++){
printf("%s(%lx) running %d\n", r->name, pthread_self(), i);
usleep(r->time);
} //return (void *)0;
return (void *)(r->end - r->start);
} int main(void)
{
int err;
pthread_t rabbit, turtle; ///< 定义线程标识符
RaceArg r_a = {"rabbit", (int )(drand48() * 100000000), 20, 50};
RaceArg t_a = {"turtle", (int )(drand48() * ), , }; /** 创建 rabbit 线程 */
if((err = pthread_create(&rabbit, NULL, th_fn, (void *)&r_a)) != ){
perror("pthread_create error");
} /** 创建 turtle 线程 */
if((err = pthread_create(&turtle, NULL, th_fn, (void *)&t_a)) != ){
perror("pthread_create error");
} sleep(); int result;
pthread_join(rabbit, (void *)&result);
printf("rabbit distance is %d\n", result);
pthread_join(turtle, (void *)&result);
printf("turtle distance is %d\n", result);
printf("reace finished\n"); /** 主控线程调用 pthread_join(), 自己会阻塞,直到 rabbit 线程结束方可运行 */
//pthread_join(rabbit, NULL);
//pthread_join(turtle, NULL); printf("control thread id: %lx\n", pthread_self());
printf("finisheld!\n");
return ;
}
三十六、Linux 线程——线程基本概念及线程的创建和终止的更多相关文章
- Linux学习之CentOS(二十六)--Linux磁盘管理:LVM逻辑卷的创建及使用
在上一篇随笔里面 Linux学习之CentOS(二十五)--Linux磁盘管理:LVM逻辑卷基本概念及LVM的工作原理,详细的讲解了Linux的动态磁盘管理LVM逻辑卷的基本概念以及LVM的工作原理, ...
- “全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java进阶(三十六)深入理解Java的接口和抽象类
Java进阶(三十六)深入理解Java的接口和抽象类 前言 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太 ...
- 《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
1.简介 前边几篇文章是宏哥自己在本地弄了一个单选和多选的demo,然后又找了网上相关联的例子给小伙伴或童鞋们演示了一下如何自动化测试,这一篇宏哥在网上找了一个问卷调查,给小伙伴或童鞋们来演示一下.上 ...
- 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索
第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果.时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微软 ...
- NeHe OpenGL教程 第三十六课:从渲染到纹理
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- Gradle 1.12用户指南翻译——第三十六章. Sonar Runner 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- 第三百三十六节,web爬虫讲解2—urllib库中使用xpath表达式—BeautifulSoup基础
第三百三十六节,web爬虫讲解2—urllib库中使用xpath表达式—BeautifulSoup基础 在urllib中,我们一样可以使用xpath表达式进行信息提取,此时,你需要首先安装lxml模块 ...
- centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 第三十六节课
centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 ...
- “全栈2019”Java第三十六章:类
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
随机推荐
- luogu2282/bzoj1219 历史年份 (dp+hash+二分+线段树)
luogu1415 拆分数列的加强版 先考虑弱化版怎么做 设f[i]表示某一串数,最后一个数的右端点是i时,它的左端点的最大值(也就是说,这一串数的最后一个数尽量小) 那么有$f[j]=max\{i+ ...
- NOIP2017题解
T1小凯的疑惑 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小 凯想知道在无法准确支付的物品中, ...
- Libre OJ 130、131、132 (树状数组 单点修改、区间查询 -> 区间修改,单点查询 -> 区间修改,区间查询)
这三题均可以用树状数组.分块或线段树来做 #130. 树状数组 1 :单点修改,区间查询 题目链接:https://loj.ac/problem/130 题目描述 这是一道模板题. 给定数列 a[1] ...
- 编写高质量代码:改善Java程序的151个建议 --[98~105]
建议的采用顺序是List中泛型顺序依次为T.?.Object (1).List是确定的某一个类型 List表示的是List集合中的元素都为T类型,具体类型在运行期决定:List<?>表示的 ...
- Linux下无法运行Color picker
➜ ~ com.github.ronnydo.colorpicker com.github.ronnydo.colorpicker: error while loading shared librar ...
- A1017. Queueing at Bank
Suppose a bank has K windows open for service. There is a yellow line in front of the windows which ...
- java面试——问题回溯
背景:用来记录面试过程中遇到的问题,在这里进行记录,下次不要犯同样的错误. 迪普科技 Linux服务器下的top命令 #动态更新的虚拟文件实际上是许多其他内存相关工具(如:free / ps / to ...
- 第三十八篇-logcat的使用
很多時候,程序有问题时都需要debug,一般会设置几个信息点,查看程序是否运行,之前学过Toast,可以广播,但是终归是不太方便,今天学习一下logcat的用法. logcat里面是一些日志,内容太多 ...
- Day033--Python--进程
什么是进程? 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体 ...
- C++基础知识-Day8
2.类的作用域运算符 shadow 在我们之前讲的内容中,我们会发现一种情况,就是在我们在不同类中的打印函数我们都是尽量让其名字不同,那么为什么会有这种情况呢?首先我们来看一个函数 void func ...