浅谈Linux线程
2023-07-19 18:24:33 115浏览
一、什么是线程?
1. 什么是进程?
程序是经过源代码进行编译链接的出来的一个文件,是静态的,而通过执行可执行程序,就获得了一个进程(process),这个进程是动态的,进程是操作系统分配给自己的实例,也可以说进程是程序的基本执行实体,是程序的一个执行实例。
/usr/src/kernels查看内核源码
进程信息被放在一个叫进程控制块的数据结构中,称其为PCB(process control block),Linux操作系统下的PCB为结构体:task_struct(使用了双向链表),task_struct是Linux内核的一种数据结构,他会被装载到RAM(内存)里并且包含着进程的信息。
下载内核源码:https://vault.centos.org/,然后通过此篇博客编译安装。在linux-3.10.0-862.el7.centos.x86_64/include/linux/sched.h查看内核源码。
task_struct描述进程的如下内容: 标识符:描述进程的唯一标识符,用来区别其他进程; 状态:任务状态,退出代码,退出信号等; 优先级:相对于其他进程的优先级; 程序计数器:程序中即将被执行的下一条指令; 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块指针; 上下文数据:进程执行时处理器的寄存器中的数据; I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表; 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等; 其他信息。
可以通过ps系列命令查看进程。如ps aux,ps -ef。
综上,进程就是PCB,是程序运行的动态描述,Linux下是一个task_struct。
2. 线程
举一个不太恰当的栗子,如果把工厂看作是进程,那么工人即是线程。
那么什么才是线程呢?在一个程序里一个执行路线或执行流程就叫做线程(thread),上面讲过进程是PCB,是程序运行的动态描述,进程是程序的一个执行实例,所以可以说线程就是进程的一条执行流,然而Linux下的执行流是通过PCB实现的,一个进程可以有多个PCB(执行流),他们共享了进程中的大部分资源,相较于传统PCB更为轻量化,因此也说Linux下的线程是轻量级进程。
因此进程是系统进行资源分配的基本单元,线程是操作系统(CPU)进行调度的基本单元。
线程间的独有: 1.标识符; 2.栈; 3.寄存器(上下文信息,程序计数器); 4.信号屏蔽字; 5.errno; 6.线程优先级。
线程间的共享: 1.虚拟地址空间; 2.I/O信息,file_struct*文件描述符表; 3.信号处理方式(信号针对整个进程); 4.工作路径。
多线程进行多任务处理 优点: 1.线程间通信更加灵活(包括进程间通信方式在内,还有全局变量和函数传参); 2.线程的创建与销毁成本更低; 3.同一进程中的线程间切换调度成本更低。 缺点: 1.性能损失,多任务处理中执行流不是越多越好,CPU资源有上限,执行流多了会增加切换调度成本; 2.健壮性低,线程之间缺乏保护; 3.缺乏访问控制,进程是访问控制的基本粒度。
线程间的切换调度:调度算法:
多进程进行多任务处理优点
健壮性高,稳定性高,独立性高,应用场景:对于主程序安全性要求高的场景——shell,服务器。
二、Linux线程控制
Linux系统实际并没有向上提供用于线程控制的接口,因为Linux下并没有线程这一概念(轻量级进程),因此有一群伟大的程序员针对系统调用接口进行封装,在上层用户态封装了线程库提供了线程控制的各个接口。
1.线程的创建
库函数:int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void * ( * start_routine) (void * ), void * arg);参数解释:
thread:用于获取线程ID(线程的操作句柄);pthread_t类型:
可以看出pthread_t 实际为无符号长整型; attr:设置线程属性,设为NULL表示使用默认属性; start_routine:线程入口函数,线程启动后要执行的函数,有个void* 的参数; arg:传给线程入口函数的参数。返回值:
成功返回0,失败返回非0值错误码。
主线程和普通线程谁先运行不一定,cpu根据调度优先级运行。
eg:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_entry(void *arg)
{
char *ptr = (char*)arg;
while(1)
{
printf("我是普通线程!:%s\n",ptr);
sleep(1);
}
}
int main(int argc, char* argv[])
{
pthread_t tid;
int ret;
char *ptr = "第一次创建线程!~\n";
ret = pthread_create(&tid, NULL, thread_entry, ptr);
if(ret != 0)
{
printf("create failed!\n");
return -1;
}
while(1)
{
printf("我是主线程!~\n");
sleep(1);
}
return 0;
}
代码中的tid和上图的轻量级进程ID不是一个概念;tid线程ID存的是每个线程独有的栈空间的首地址,而轻量级进程ID是pid_t的类型和之前的进程ID是一个概念。 我们打印一下tid:
2.线程的退出
1. 线程入口函数运行完毕则线程就会退出,因此在线程入口函数中return就可以退出线程。(main函数return退出的是进程);
2. 在任意位置调用接口(该接口谁调用谁退出): void pthread_exit(void * retval); retval是线程的返回值;
3. 在任意位置调用接口: int pthread_cancel(pthread_t tid);取消一个执行中的线程,成功返回0,失败返回错误码;
需要注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
3.线程等待
等待指定的一个线程退出,获取退出线程的返回值,回收资源。
why什么需要线程等待?因为已经退出的线程,其空间没有被释放,仍然在进程的地址空间内;创建的新的线程不会复用刚才退出线程的地址空间。
在创建一个线程时,线程有个默认属性——分离属性,默认值为joinable,处于joinable状态的线程退出后不会自动释放资源,需要被等待。
线程等待函数: int pthread_join(pthread_t thread, void ** retval) 参数: thread:线程ID; retval:指向一个指针,后者指向线程的返回值。 返回值: 成功返回0,失败返回错误码。
我们在上面代码的基础上对其进行线程等待
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
int func()
{
char *ptr = "马上要下课了!\n";//设置为常量而不是局部变量
pthread_exit(ptr);
return 3;
}
void *thread_entry(void *arg)
{
char *ptr = (char*)arg;
while(1)
{
sleep(10);
func();
//return NULL;//第一种退出方式
//pthread_exit(NULL);//第二种退出方式NULL是线程的返回值
printf("我是普通线程!:%s\n",ptr);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
int ret;
char *ptr = "第一次创建线程!~\n";
ret = pthread_create(&tid, NULL, thread_entry, ptr);
if(ret != 0)
{
printf("create failed!\n");
return -1;
}
//sleep(5);//第三种退出方式
//pthread_cancel(tid);
void *retval;
pthread_join(tid,&retval);
printf("retval:%s\n",(char*)retval);
printf("%p\n", tid);
while(1)
{
printf("我是主线程!~\n");
sleep(1);
}
return 0;
}
4.线程分离
将指定线程的分离属性设置为detach状态,处于detach状态的线程退出后自动释放资源,不需要等待。
函数:int pthread_detach(pthread_t thread)
参数:线程ID; 返回值:成功返回0,失败返回错误代码。
int main(int argc, char* argv[])
{
pthread_t tid;
int ret;
char *ptr = "第一次创建线程!~\n";
ret = pthread_create(&tid, NULL, thread_entry, ptr);
if(ret != 0)
{
printf("create failed!\n");
return -1;
}
pthread_detach(tid);//线程分离
//sleep(5);//第三种退出方式
//pthread_cancel(tid);
void *retval;
ret = pthread_join(tid,&retval); //线程等待
if(ret == EINVAL)//判断该线程是否已经被分离
{
printf("该线程已经被分离\n");
}
printf("retval:%s\n",(char*)retval);
printf("%p\n", tid);
while(1)
{
printf("我是主线程!~\n");
sleep(1);
}
return 0;
}
如果不关心线程的返回值,则可以直接在线程的入口函数进行分离。pthread_detach(pthread_self());
pthread_self()该接口可以用于获取线程自身的线程ID
当我们对线程的返回值不关心并且我们也不想等待线程的时候分离线程。
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论