Thread与Linux软件差别,进程和线程的区别 ( LINUX系统 )

提到进程和线程的区别,有句很经典的话:进程是资源分配的基本单位,线程是调度的基本单位。那么它俩本质上是怎么区分的呢?

下面就让我们来看一下Linux下进程(process)和线程(thread)的本质区别。

一 进程(process)

首先看下《LKD》里对进程的定义,

A process is a program (object code stored on some media) in the midst of execution.

也就是处于运行状态的程序。除了程序代码,进程还包含很多其它资源,依然来自《LKD》里的定义,

They also include a set of resources such as open files and pending signals,

internal kernel data, processor state, a memory address space with one or more memory

mappings, one or more threads of execution, and a data section containing global variables.

ps:我们写程序时有时会加上打印输出,其实就是用的进程的资源—open files去输出的,也就是标准输出流句柄

从这可以看出进程就是正在执行的程序加上相关资源的一个集合体,

746afd11d8c074455397422d4bf84aa3.png

内核为每个进程生成一个进程描述符(process descriptor),其类型是struct task_struct。进程描述符是个很大的结构体,包含了进程所有相关的信息。

内核会把进程描述符放到一个叫task_list的双向循环链表里。(图片来自《LKD》)

a0680786ccb666e2e5828ccddbbc5608.png

这个task_list就是给CPU来调度的,可以说CPU调度的就是这些进程描述符。

二 线程(thread)

先看下《LKD》里线程的定义,

Threads of execution, often shortened to threads, are the objects of activity within the process. Each thread includes a unique program counter, process stack, and set of processor registers.

线程是进程里的活动对象。为什么这么说?因为我们都知道同一进程里的线程之间是可以直接共享资源的,不需要像进程间共享资源搞出很多方法,所以从外部来看线程是处于进程里运行的。

Linux内核为线程也生成进程描述符,这个进程描述符也会放到task_list里。这是linux线程的特别之处,并不区分线程和进程,因为两者都有进程描述符。

到这里,可能有点晕了,可以看下一节的图释。

三 解释

假设我们有个程序代码如下,

#include

int main(void)

{

printf("hello world\n");

return 0;

}

编译完后运行,那么这个程序对应的进程示意图如下(蓝色部分表示与资源相关的成员,大部分是指针),

b39b9bddff009c9299512b2cf4be5245.png

这段程序没有创建线程,只有一个进程描述符,进程描述符里与资源相关的成员会指向该进程所拥有的资源。

下面我们看一个创建线程的程序代码,

#include

#include

#include

int share = 100;

void * thread_fn(void *arg)

{

printf("hello %s: %d\n", __func__, share);

}

int main()

{

pthread_t tid;

int err;

err = pthread_create(&tid, NULL, thread_fn, NULL); // 创建线程

if (err != 0)

{

printf("Error: in pthread_create()\n");

return 1;

}

printf("hello %s: %d\n", __func__, share);

sleep(1);

return 0;

}

输出如下,

4ba2dea0b4f2a942ec79acee8d40fd92.png

运行时这个程序对应的进程示意图如下,

02edd41dd7a539213748f17dfa756b48.png

程序开始运行时linux会生成进程描述符1,运行过程中创建线程去执行thread_fn(),此时linux会生成进程描述符2。这两个进程描述符里与资源有关的成员都指向相同的资源,这就是线程间共享资源的原理。

对于没有创建线程的例子,也可以认为该进程里只包含一个线程,称为单线程进程,进程里的资源被这个单线程独享。

从全局来看,操作系统上一般会有多个程序在运行,反映到内核里,如下图所示

12e872dab24ef6a8ad1adb63553c3c61.png

在task_list里存放进程描述符,有的属于单线程进程的,有的属于多线程进程里单个线程的。

对于内核来说,它分配资源时是按照进程来分配的,分配完资源后如果进程里创建了多个线程,那么多个线程间就共享相同的资源,向外表现为一个进程里多个线程在运行。而调度就是调度task_list里的一个个进程描述符,所以CPU调度的基本单位是线程,不是进程(单线程进程也可以视为独立线程)。

ps:从内核的视角来看,没有外面的红色虚线框,内核只负责调度单个的进程描述符,而不关心这个进程描述符属于谁的。

9783402f3828adb7b94503b13e4c1359.png

四 例子

每个进程都有自己独一无二的进程id号,存在进程描述符(也就是struct task_struct)里的pid成员里,

4edf919fcf31d3a63b33b58104c9c5a3.png

pid就是进程id号,tgid是线程组id号(thread group)

对于单线程进程来说,进程描述符里的pid和tgid值相等

对于多线程进程来说,每个线程也有自己的pid,而其tgid则是和主线程的pid相等(程序开始运行时是个单线程进程,此时的这个线程就是主线程)

下面的代码展示线程的pid和tgid,

#include

#include

#include

#include

void * thread_fn(void *arg)

{

printf("thread_fn() pid: %d, thread pid: %lu\n", getpid(), syscall(SYS_gettid));

}

int main()

{

pthread_t tid;

int err;

err = pthread_create(&tid, NULL, thread_fn, NULL); // 创建线程

if (err != 0)

{

printf("Error: in pthread_create()\n");

return 1;

}

printf("main() pid: %d\n", getpid());

sleep(1);

return 0;

}

输出如下,

ce78b020ce773e2f53cb4b2ad3bb2726.png

可以看出主线程里调用getpid()得到的是主线程的进程描述符里的pid值,而在执行thread_fn的线程里调用getpid()返回的则是该线程的进程描述符里的tgid值,这个值和主线程的pid是相等的。

想要查看线程自己本身的pid,需要使用syscall(SYS_gettid)函数调用,当然还有一些其它方法,可以看出线程自身也有pid,且与主线程的pid值不一样,这也证明了内核给每个线程都会分配一个进程描述符。

五 总结

只要抓住进程描述符和相关资源这2个概念,就很容易弄懂linux下进程和线程的区别了。(似乎叫线程描述符更合适点)

如果有写的不对的地方,希望能留言指正,谢谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值