linux线程组和进程区别,Linux线程 之 线程 线程组 进程 轻量级进程(LWP)

2023-11-02 07:00

本文主要是介绍linux线程组和进程区别,Linux线程 之 线程 线程组 进程 轻量级进程(LWP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线程是有栈的,我们知道,普通的一个进程,它的栈空间是8M,我们可以通过ulmit -a查看:

stack size (kbytes, -s) 8192线程也不例外,线程也是需要栈空间的这句话是废话,呵呵。对于属于同一个进程(或者说是线程组)的多个线程他们是共享一份虚拟内存地址的,如下图所示。这也就决定了,你不能无限制创建线,因为纵然你什么都不做,每个线程默认耗费8M的空间(事实上还不止,还有管理结构,后面陈述)。Ulrich Drepper大神有篇文章《Thread numbers and stacks》,分析了线程栈空间方面的计算。如果我们真的需要很多个线程的话,幸好我们还是可以做一些事情。我们可以通过pthread_attr_setstacksize,设定好stack size属性然后在pthread_create.

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

441441

线程栈如上图所示,共享进程(或者称之为线程组)的虚拟地址空间。既然多个线程聚集在一起,我怎么知道我要操作的那个线程栈的地址呢。要解决这个问题,必须要领会线程和进程以及线程组的概念。我不想写一堆片汤话,下面我运行我的测试程序,然后结合现象分析原因:

#include

#include

#include

#include

#define gettid() syscall(__NR_gettid)

pthread_key_t key;

__thread int count = 2222;

__thread unsigned long long count2 ;

static __thread int count3;

void echomsg(int t)

{

printf("destructor excuted in thread %x,param=%x\n",pthread_self(),t);

}

void * child1(void *arg)

{

int b;

int tid=pthread_self();

printf("I am the child1 pthread_self return %p gettid return %d\n",tid,gettid());

char* key_content = malloc(8);

if(key_content != NULL)

{

strcpy(key_content,"ACACACA");

}

pthread_setspecific(key,(void *)key_content);

count=666666;

count2=1023;

count3=2048;

printf("I am child1 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n",tid,&count,count,&count2,count2,&count3,count3);

asm volatile("movl %%gs:0, %0;"

:"=r"(b) /* output */

);

printf("I am child1 , GS address %x\n",b);

sleep(2);

printf("thread %x returns %x\n",tid,pthread_getspecific(key));

sleep(50);

}

void * child2(void *arg)

{

int b;

int tid=pthread_self();

printf("I am the child2 pthread_self return %p gettid return %d\n",tid,gettid());

char* key_content = malloc(8);

if(key_content != NULL)

{

strcpy(key_content,"ABCDEFG");

}

pthread_setspecific(key,(void *)key_content);

count=88888888;

count2=1024;

count3=2047;

printf("I am child2 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n",tid,&count,count,&count2,count2,&count3,count3);

asm volatile("movl %%gs:0, %0;"

:"=r"(b) /* output */

);

printf("I am child2 , GS address %x\n",b);

sleep(1);

printf("thread %x returns %x\n",tid,pthread_getspecific(key));

sleep(50);

}

int main(void)

{

int b;

pthread_t tid1,tid2;

printf("hello\n");

pthread_key_create(&key,echomsg);

asm volatile("movl %%gs:0, %0;"

:"=r"(b) /* output */

);

printf("I am the main , GS address %x\n",b);

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);

printf("pthread_create tid1 = %p\n",tid1);

printf("pthread_create tid2 = %p\n",tid2);

sleep(60);

pthread_key_delete(key);

printf("main thread exit\n");

return 0;

}这是一个比较综合的程序,因为我下面要多次从不同的侧面分析。对于现在,我们要展示的是进程 线程 线程组的关系。在一个终端运行编译出来的test2程序,显示的信息如下:

441441

另一个终端看ps信息,ps显示的信息如下:

441441

直接ps,是看不到我们创建的线程的。只有3658一个进程。当我们采用ps -eLf的时候,我们看到了三个线程3658/3659/3660,或者称之为轻量级进程(LWP)。Linux到底是怎么看待这三者的关系的呢:

Linux下多线程程序,一般都是有一个主进程通过调用pthread_create创建了一个或者多个子线程,如同我们的程序,主进程在main中创建了两个子进程。那么Linux到底是怎么看待这些事情的呢?

pid_t pid;

pid_t tgid;

...

struct task_struct *group_leader; /* threadgroup leader */上面三个变量是进程描述符的三个成员变量。pid字面意思是process id,其实叫thread id会更合适。tgid 字面含义是thread group ID。对于存在多个线程的程序而言,每个线程都有自己的pid,没错pid,如同我们例子中的3658/3659/3660,但是都有个共同的线程组ID (TGID):3658 。

好吧,我们再重新说一遍,对于普通进程而言,我们可以称之为只有一个LWP的线程组,pid是它自己的pid,tgid还是它自己,线程组里面只有他自己一个光杆司令,自然group_leader也是它自己。但是多线程的进程(线程组更恰当)则不然。开天辟地的main函数所在的进程会有自己的PID,也会有也TGID,group_leader,都是他自己。注意,它自己也是LWP。后面他使用ptherad_create创建了2个线程,或者LWP,这两个新创建的线程会有自己的PID,但是TGID会沿用创建自己的那个进程的TGID,group_leader也会尊创建自己的进程的进程描述符(task_struct)为自己的group_leader。copy_process函数中有如下代码:

p->pid = pid_nr(pid);

p->tgid = p->pid;//普通进程

if (clone_flags & CLONE_THREAD)

p->tgid = current->tgid;//线程选择叫起它的进程的tgid作为自己的tgid

....

p->group_leader = p;//普通进程

INIT_LIST_HEAD(&p->thread_group);

...

if (clone_flags & CLONE_THREAD) {

current->signal->nr_threads++;

atomic_inc(&current->signal->live);

atomic_inc(&current->signal->sigcnt);

p->group_leader = current->group_leader;//线程选择叫起它的进程作为它的group_leader

list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);

}OK,ps -eLf中有个字段叫NLWP,就是线程组中LWP的个数,对于我们的例子,main函数所在LWP+两个线程 = 3.

我们传说的getpid函数,本质取得是进程描述符的TGID,而gettid系统调用,取得才是每个LWP各自的PID。请看上面的图片输出,上面连个线程gettid返回的是3873和3874,是自己的PID。稍微有点毁三观

除此外,需要指出的是用户态pthread_create出来的线程,在内核态,也拥有自己的进程描述符task_struct(copy_process里面调用dup_task_struct创建)。这是什么意思呢。意思是我们用户态所说的线程,一样是内核进程调度的实体。进程调度,严格意义上说应该叫LWP调度,进程调度,不是以前面提到的线程组为单位调度的,本质是以LWP为单位调度的。这个结论乍一看惊世骇俗,细细一想,其是很合理。我们为什么多线程?因为多CPU,多核,我们要充分利用多核,同一个线程组的不同LWP是可以同时跑在不同的CPU之上的,因为这个并发,所以我们有线程锁的设计,这从侧面证明了,LWP是调度的实体。

我们用systemtap去观察下test2程序相关的调度:systemtap脚本如下:

这篇关于linux线程组和进程区别,Linux线程 之 线程 线程组 进程 轻量级进程(LWP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/328926

相关文章

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Python中@classmethod和@staticmethod的区别

《Python中@classmethod和@staticmethod的区别》本文主要介绍了Python中@classmethod和@staticmethod的区别,文中通过示例代码介绍的非常详细,对大... 目录1.@classmethod2.@staticmethod3.例子1.@classmethod

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用