深入浅出线程原理

Linux 中的线程本质

线程接口由 Native POSIX Thread Library 提供,即:NPTL 库函数

线程被称为轻量级进程 (Light Weight Process)

每一个线程在内核中都对应一个调度实体,拥有独立的结构体 (task_struct)

  • 内核设计:一个进程对应内核中的一个结构体,对应一个进程标识
  • 引入线程:线程是内核的调度实体,在内核中必然对应一个结构体

用户模式:一个进程中存在多个线程

内核模式:每个线程是一个独立的调度实体

另一个视角

拥有多线程的进程,又被称为线程组 (谁是线程组长?)

在内核数据结构 task_struct 中存在 pid 和 tgid:

pid_t pid => 线程标识符 (Thread ID)

pid_t tgid => 线程组标识符 (Thread Group ID)

进程创建后默认拥有一个线程,即:主线程 (默认执行流)

主线程的 LWP 标识与进程标识符相同,即:主线程为线程组长

其他子线程创建后隶属于当前进程:

  • 子线程的 LWP 标识符各不相同,且与进程标识符相同
  • 子线程调用 getpid() 的结果相同 

问题

对于线程来说,pthread_t 类型的标识符 与 pid_t 类型标识有什么不同?

pid_t 是内核使用的标识符,是全局唯一的;而 pthread_t 是应用程序中使用的标识符,是局部唯一的

线程标识符实验

test1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <pthread.h>

pid_t gettid(void)
{
    return syscall(SYS_gettid);
}

void* thread_entry(void* arg)
{
    pid_t pid = getpid();
    pid_t tid = gettid();
    pthread_t self = pthread_self();
    
    printf("thread:pid = %d\n", pid);
    printf("thread:tid = %d\n", tid);
    printf("thread:self = %ld\n", self);
    
    return NULL;
}

int main()
{
    pthread_t t = 0;
    pthread_t self = pthread_self();
    pid_t pid = getpid();
    pid_t tid = gettid();
    
    printf("main:pid = %d\n", pid);
    printf("main:tid = %d\n", tid);
    printf("main:self = %ld\n", self);
    
    pthread_create(&t, NULL, thread_entry, NULL);
    
    printf("main:t = %ld\n", t);
    
    pthread_join(t, NULL);
    
    return 0;
}

第 11 行,由于 Linux 系统中没有提供获取线程内核标识符的函数,所以我们使用系统调用的方式实现了获取线程内核标识符的功能,函数名为 gettid()

pthread_self() 函数用于获取应用程序中使用的线程标识符,getpid() 函数用于获取进程标识符,gettid() 函数用于获取内核中使用的线程标识符

程序运行结果如下图所示:

通过打印可以看出,主线程的 pid 和 tid 相同,pid 和 tid 相同,则说明该线程为主线程;内核和应用程序中使用的线程标识符的值不同,说明它们不是同一个标识符

使用 ps 命令查看线程信息

-e => Select all processes

-L => Show threads,possibly with LWP and NLWP columns

-f => Do full-format listing

ps -eLf => to get info about threads

test2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <pthread.h>

pid_t gettid(void)
{
    return syscall(SYS_gettid);
}

void* thread_entry(void* arg)
{   printf("thread: %ld\n", pthread_self());
    while(1)
    {
        pid_t pid = getpid();
        pid_t tid = gettid();
    
        printf("pid = %d, tid = %d\n", pid, tid);
        sleep(5);
    }
    
    return NULL;
}

int main()
{
    int i = 0;
    pthread_t t = 0;
    pid_t pid = getpid();
    pid_t tid = gettid();
    
    printf("main:pid = %d\n", pid);
    printf("main:tid = %d\n", tid);
    
    for(i=0; i<4; i++)
        pthread_create(&t, NULL, thread_entry, NULL);
    
    while(1)
        sleep(1);

    return 0;
}

该程序创建了 4 个子线程,我们运行该程序,然后使用命令 ps -eLf 来查看线程的信息

LWP 是线程标识符,NLWP 该进程有 n 个线程

a.out 进程一共有 5 个线程,它们的进程标识符相同,线程标识符各不相同

实验程序

我们 kill 掉某个子线程,然后整个进程都终止运行了

多线程内核模型

Linux 内核中的基本调度单位为 task_struct,即:内核中以 "任务" 作为调度的基本单位

一个可执行程序加载执行得到一个进程,加载的时候需要去申请系统资源,其中一个很重要的资源就是内存资源,需要构建进程地址空间,然后内核构造一个 task_struct 结构体去创建一个主线程执行,而创建子线程的时候,不需要去申请系统资源了,直接构造 task_struct 结构体,然后将结构体中指向地址空间的指针指过来即可,所以在内核中,线程是一个轻量级的进程,并且线程共享进程的系统资源

值得思考的问题

多线程之间是否有 "父子关系" ?

答:无!

进程中只有主线程和子线程,线程之间没有 "父子关系"

主线程如果先于子线程结束会发生什么?

Linux 中主线程如果执行结束,则进程结束

进程结束则进程资源被释放,子线程被迫结束

使用 kill 命令是否能够 "杀死" 指定线程 ?

kill 命令默认发送信号 SIGTERM,而信号的目标是进程

因此,kill 任意子线程的 pid_t 将导致整个进程结束

pthread_t 究竟是什么数据类型 ?

pthread_t 是 POSIX Thread 中的接口,具体定义与系统相关

通常情况下,pthread_t 的具体定义是 一个整型值

  • Linux 中 pthread_t 的具体定义是 64 位整数 (保存地址值)
  • 其他系统中 pthread_t 直接映射为 Task ID 值
  • 对于一些特殊的系统,pthread_t 是一个结构体

Linux 多线程 API 函数

线程标识相等判断

int pthread_equal(pthread_t t1, pthread_t t2);

如果 t1 和 t2 相等,返回非 0 值,否则返回 0 值

注意:

Linux 系统编程时,可直接用 t1 == t2 的方式判断是否相等

编写可移植的多线程代码,使用 pthread_equal() 判断是否相等

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值