线程
线程的概述
在之前,我们常把进程定义为 程序执行的实例,实际不然,进程实际上只是维护应用程序的各种资源,并不执行什么。真正执行具体任务的是线程。
那为什么之前直接执行a.out的时候,没有这种感受呢?
那是因为每一个进程中都会有一个主线程,我们默认执行的就是这个主线程。
线程创建比进程简单
进程通过返回值确定 是哪块进程的代码。
线程不需要,创建一个线程,比较简单,像回调函数一样,调用线程创建函数,在对应函数体中 操作这一线程即可。
从这往下的概述部分 重点(理解背诵)
进程是系统分配资源的基本单位,线程是CPU执行基本调度的基本单位
比如 如果线程是具体某个人,那么进程就是指部门
线程可以看作一个轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。
进程 必须至少包含一个线程
线程依赖于进程,线程共享进程的资源,线程的系统资源有(计数器,一组寄存器和栈)
进程结束 当前进程的所有线程 都将立即结束
Linux内核是不区分进程和线程的,只有在用户层面上进行区分。所以,进程所有操作函数pthread*是库函数,而并非系统调用
线程共享资源
- 文件描述符
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID 内存地址空间
线程非共享资源
- 线程id
- 处理器现场和栈指针
- 独立的栈空间
- errno变量
- 信号屏蔽字
- 调度优先级
线程被CPU调度,因此线程中有调度优先级,且线程间不共享
查看指定进程的线程号的命令:ps -Lf pid(进程号)
线程的API
API介绍用的代码 较简短的代码我用图片展示。
只要看到了pthread.h 头文件,我们在编译的时候就需要加上 -lpthread
pthread_t 是无符号长整型
1、查看线程号
#include <pthread.h>
pthread_t pthread_self(void);
功能:
查看线程号
参数:
无参
返回值:
调用该函数的线程 的 线程ID
代码演示
代码运行结果
线程ID(通过pthread_self得到) 和 IPW(轻量级进程)的区别
大家看这张图,可以看到这两个值有明显的区别
在Linux中,线程就是LWP(轻量级进程),全局唯一,由操作系统内核分配,用于系统调度和资源管理。
而
线程ID呢仅在同一进程内有效,是抽象标识符。由 pthread 库在进程内维护。
2、创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
功能:
创建一个线程
参数:
thread:线程标识符地址
attr:线程属性结构体地址,通常设置为NULL
属性这个参数,我们现在填写NULL,下面我会详细说一下这个参数。
start_routine:线程函数的入口地址
arg:传递给线程函数的参数
返回值:
成功:0
失败:非0
代码演示 案例1
注意这里主进程一定要阻塞,因为进程结束,线程也会关闭
代码运行结果
案例二 创建进程,每个线程有自己的线程函数
代码运行结果是一样的,大家只要知道能够这样用就可以了。
3、回收线程函数
函数介绍
功能:
等待线程结束(此函数会阻塞),并回收线程资源。如果线程已结束,那么该函数会立即返回。
参数:
thread:被等待的进程的进程ID
retval:用来存储线程退出状态的指针的地址
这里细说一下:retval的返回值类型我们可以看到是void **,这个变量需要用户创建,用来存储创建函数 线程执行函数的 返回值,返回值时void*类型。由于我们要得到它,就要提前创建一个void *的变量,再通过函数修改我们创建的变量为返回值的内容,由于是函数内部要函数外部的变量的值,因此需要传递所创建void *的变量的地址,因此时void **类型。
返回值:
成功:0
失败:非0
代码演示
代码运行结果
注意
由于带阻塞,因此有顺序,如下面这种情况
先等待tid1结束,回收tid1后,才会回收tid2
不管谁先结束,都是先1 后2
进程分离
创建好线程后,当多个任务同时进行,用上面的方法,会阻塞线程的释放,导致资源浪费(长时间不适用却霸占内存),因此这里 我们就将其分离出去,把释放工作交给系统,系统发现它结束,就会释放
由于它的归属权已经归于系统,此时我们就不可以再对它使用join
注意这里的分离,并不是该线程不依赖于进程,而是将 释放线程独立资源 的权限交给了系统,进程还是依赖与进程的,依旧共享进程的空间
函数介绍
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程的独立资源回收工作与当前进程分离
参数:
thread:线程ID
返回值:
成功:0
失败:非零
代码演示 主线程和子线程
本代码将实现 主线程和子线程 一起运行,并且利用主线程的正常工作,来验证pthread_detach的不阻塞的特性
代码运行结果
4、线程的取消和退出
注意要退出线程 一定不要调用exit或者_exit 这两个是退出进程的函数,如果调用这个在线程中知道你的进程是什么,它会将进程退出,进程退出会导致所有的线程退出,那么我们该怎么让单个线程退出呢?
1、线程的退出(自杀)
#include <pthread.h>
void pthread_exit(void *retval);
函数功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段的,因此通常线程退出后,所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针(return后的数据)
返回值:
无
2、线程的取消(他杀)
取消本线程,也可以取消当前进程的其他线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段的,因此通常线程退出后,所占用的资源并不会释放。
参数:
thread:目标线程ID
返回值:
成功:0
失败:出错编号
注意
杀死线程也不是立刻就能完成,必须要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用。
代码演示:
代码功能:子线程1实现5s后自杀,子线程2在7s时杀死子线程3,子线程2在10s时杀死自己。
这里我们会与遇到一个问题:当我们在线程2 中,我们首先需要传入本线程的名字(线程2),还需要传入子线程3的线程ID,我们该如何实现传两个参数呢?
答案在代码中,大家自己查看。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
//传递两个参数的办法就是借助结构体,将线程名和ID作为结构体成员后,将结构体传入 线程调用函数 中即可
//并且如果要实现tid3的修改同步到结构体内,需要传递tid3的指针类型
typedef struct dataDouble
{
char name[32];
pthread_t *id;
}DATA;
//线程调用函数声明
void *my_fun1(void *arg);
void *my_fun2(void *arg);
void *my_fun3(void *arg);
int main(int argc, char const *argv[])
{
//创建线程ID遍历(存放线程ID)
pthread_t tid1,tid2,tid3;
DATA *tid2_data = (DATA *)calloc(1,sizeof(DATA));
tid2_data->id = &tid3;
strcpy(tid2_data->name,"子进程2");
//创建线程
pthread_create(&tid1,NULL,my_fun1,(void *)"子线程1");
pthread_create(&tid2,NULL,my_fun2,(void *)tid2_data);
pthread_create(&tid3,NULL,my_fun3,(void *)"子线程3");
//释放线程
pthread_detach(tid1);
pthread_detach(tid2);
pthread_detach(tid3);
//阻塞进程
while(1);
//释放结构体申请空间,一定要在全部线程结束之后
free(tid2_data);
return 0;
}
//线程调用函数体实现
void *my_fun1(void *arg)//线程1 在5s的时候自杀
{
int i = 0;
while(1)
{
sleep(1);
printf("----%s的运行时间为:%d\n",(char *)arg,++i);
if(i == 5)
{
pthread_exit(NULL);
}
}
}
void *my_fun2(void *arg)//线程2 在7s的时候杀死线程3,在10s的时候自杀(使用cancel)
{
DATA data = *(DATA *)arg;
int i = 0;
while(1)
{
sleep(1);
printf("--------%s的运行时间为:%d\n",data.name,++i);
if(i == 7)
{
pthread_cancel(*data.id);
}
if(i == 10)
{
pthread_cancel(pthread_self());
}
}
}
void *my_fun3(void *arg)
{
int i = 0;
while(1)
{
sleep(1);
printf("------------%s的运行时间为:%d\n",(char *)arg,++i);
}
}
代码运行结果
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!
下篇介绍:线程的属性介绍,线程池的简述,多线程的建立