Linux线程基础详解

Posted 小赵小赵福星高照~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux线程基础详解相关的知识,希望对你有一定的参考价值。

Linux线程

文章目录

Linux线程概念

什么是线程

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行

书本上的概念:线程是进程的一个执行分支,是在进程内部运行的一个执行流!

那么线程具体化点是什么?

可执行程序在磁盘当中,磁盘中的代码和数据加载到物理内存时,是可以加载在任意位置的,进程地址空间通过页表映射到代码和数据,如果我们创建进程,不独立创建地址空间,不创建用户级页表,甚至不进行IO将程序的数据和代码加载到内存,我们只创建task_struct,然后让新的PCB,指向和老的PCB指向同样的mm_struct(进程地址空间)

然后,通过合理的资源分配(当前进程的资源)让每个task_struct都能使用进程的一部分资源,此时我们的每个PCB被CPU调度的时候,执行的粒度,是不是比原始进程执行的粒度要更小一些,这就是Linux线程的原理

地址空间:站在资源的角度,其实是进程的资源窗口

如何理解线程是在进程内部运行的一个执行流?线程本质是在进程的地址空间内运行

那么什么又是进程呢?

站在OS角度:进程承担分配系统资源的基本单位,一个进程被创建好之后,后续可能内部存在多个执行流(线程)

那么如何看待我们曾经所了解的进程呢?

本质是承担系统资源的基本实体,只不过内部只有一个执行流

举个例子:在生活当中我们想找爸爸妈妈要点钱,爸爸妈妈的钱早就有了,是在社会中赚的存在卡里面,这里的社会是OS,爸爸妈妈是进程,而我们是线程。

Linux线程和其他平台的线程

Linux当中的线程:站在CPU的角度,线程和进程有区别吗,没有任何区别,在CPU看来,它看到的都是task_struct,实际上,CPU执行的时候,可能执行的进程流已经比历史变得更加轻量化了,Linux下其实是没有真正意义上的线程概念的,而是用进程来模拟的!Linux下的线程称为"轻量级进程",Linux不可能直接在OS层面提供线程系统调用接口,最多轻量级进程的调度接口,而线程操作接口是由原生线程库提供。

其他平台不一定是Linux这样的,比如windows具有真正的线程概念!

系统内可能存在大量的进程,进程:线程=1:n,如果存在批量的进程,系统一定可能存在大量的线程,OS系统就需要管理这么多的线程:通过先描述,再组织进行管理,支持真线程的系统一定要做到描述线程:TCB(线程控制块),该操作系统既要进行进程管理,又要进行线程管理,这样的系统往往比较复杂,windows上有相关线程操作的系统调用接口

如果支持真线程,数据结构里面包含什么属性呢??必须包含和进程相似的一些属性

总结

进程:本质是承担分配系统资源的基本实体,线程是OS调度的基本单位

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

因为所有的PCB都共享地址空间,理论上,每个线程都能看到进程的所有的资源,好处:线程间通信,成本特别低,缺点:一定存在大量的临界资源!势必可能需要使用各种互斥和同步机制!

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。

是不是线程越多越好?合适最好,线程与线程之间也有切换,线程越多,效率会变低

  • 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高

编写与调试一个多线程程序比单线程程序困难得多

异常

#include<iostream>
using namespace std;
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* thread_run(void *args)

    while(true)
    
        cout<<(char*)args<<endl;
        sleep(2);
        int *p;
        *p = 100;
    
    return nullptr;

int main()

    pthread_t tid;
    pthread_create(&tid,nullptr,thread_run,(void*)"thread 1");
    
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

我们发现新线程异常导致主线程也退出了,这是为什么呢?

线程是进程的一个执行分支,野指针,除0等异常操作导致线程退出的同时,也意味着进程触发了该错误,进而导致进程退出

进程和线程的区别

进程是资源分配的基本单位

线程是调度的基本单位

线程共享进程数据,但是也拥有自己的一部分资源:

  1. 上下文数据

这部分数据独立说明线程是可以切换的

  1. 独立的栈结构

独立的栈结构说明线程是独立运行的

  1. 信号屏蔽字

  2. 调度优先级

进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表共享

需要注意的是多进程不共享文件描述符表,但是表里面的内容可以是一样的,而多线程共享文件描述符表

  • 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)

  • 当前工作目录

  • 用户id和组id

进程和线程的关系图:

pthread线程库

原生线程库是在应用层实现的线程库,不负责真正的在操作系统上帮你创建线程,而是给用户提供一些创建线程的接口,Linux是不会提供出一些创建线程的接口的,但是可以创建出一些轻量级进程接口(vfork)

pthread_create

创建一个线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

参数:

thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

第一个是输出型参数,第二个线程属性,第三个参数是函数指针

下面我们写一个创建线程的程序:

Makefile的编写:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f mythread

-lpthread链接线程库,g++编译程序成为.o目标文件,然后在链接阶段找线程操作的相关函数实现

#include<iostream>
using namespace std;
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* thread_run(void *args)

    while(true)
    
        cout<<(char*)args<<endl;
        sleep(2);
    
    return nullptr;

int main()

    pthread_t tid;
    pthread_create(&tid,nullptr,thread_run,(void*)"thread 1");
    
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

可以看到主线程睡眠一秒,新线程睡眠两秒,就比主线程少打印一次

我们ps axj查看一下线程信息:

我们发现只有一个,为什么呢?

是因为ps ajx查到的是进程,ps -aL是查看系统轻量级进程的选项:

此时我们就看到了两个线程,需要注意的是这两个线程的PID相等,而这两个线程有个LWP。这是什么呢?PID相等,说明两个mythread本质是属于一个进程的

LWP表示执行流是一个轻量级进程,标识唯一性,CPU调度,看的就是LWP

那么这个创建线程的输出型参数是什么呢?下面我们来打印一下它:

#include<iostream>
using namespace std;
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* thread_run(void *args)

    while(true)
    
        cout<<(char*)args<<endl;
        sleep(2);
        //int *p;
        //*p = 100;
    
    return nullptr;

int main()

    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    
    pthread_create(&tid1,nullptr,thread_run,(void*)"thread 1");    									pthread_create(&tid2,nullptr,thread_run,(void*)"thread 2");
    pthread_create(&tid3,nullptr,thread_run,(void*)"thread 3");
    pthread_create(&tid4,nullptr,thread_run,(void*)"thread 4");
    
    printf("%d\\n",tid1);
    printf("%d\\n",tid2);
    printf("%d\\n",tid3);
    printf("%d\\n",tid4);
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

我们打印一下这些tid到底是什么?

我们发现是一些很大的数字,注意:

LWP和tid是不一样的,LWP是Linux区别轻量级进程的唯一性,tid实际上是应用层管理库当中线程的相关标识符

我们以地址(%p)形式打印tid:

我们感觉tid有可能是一种地址数据,这个tid我们后面再说它是什么,下面我们先来看线程终止:

线程终止

线程终止有多种方法,线程退出的方法:

  • 线程退出,从自己的例程中return,就是线程退出

那我们可以在线程例程中调用exit进行终止线程吗?

注意

不可以调用exit终止线程,这样会将也进程(主线程)终止,下面将会验证

  • 调用pthread_exit

终止线程

我们首先创建五个线程来验证线程终止:

return终止:

#include<iostream>
using namespace std;
#include<cstdio>
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* ThreadRoutine(void *args)

    //暂时的方案
    int i = *(int*)args;
    delete (int*)args;
    int cnt = 0;
    while(cnt < 5)
    
        cout<<"thread index : "<< i <<"count"<<cnt<<endl;
        sleep(1);
        cnt++;
    
    return nullptr;

int main()

#define NUM 5
    pthread_t tids[NUM];
    //创建5个线程
    for(auto i = 0;i<NUM;i++)
    
        int *p = new int(i);
    	pthread_create(tids+i,nullptr,ThreadRoutine,p);  
    
    
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

我们打印结果看到五个线程已经退出了,已经不在打印了,但是我们在ps -aL可以看到还存在呢,这几个线程的资源并没有回收

所以后面还要线程等待

不能exit退出的验证:

#include<iostream>
using namespace std;
#include<cstdio>
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* ThreadRoutine(void *args)

    //暂时的方案
    int i = *(int*)args;
    delete (int*)args;
    int cnt = 0;
    while(cnt<5)
    
        cout<<"thread index : "<< i <<"count"<<cnt<<endl;
        sleep(1);
        cnt++;
    
    cout<<"thread: "<<i<<"quit"<<endl;
    exit(0);
    //return nullptr;//线程退出:从自己的例程中return就代表线程退出

int main()

#define NUM 5
    pthread_t tids[NUM];
    for(auto i = 0;i<NUM;i++)
    
        int *p = new int(i);
    	pthread_create(tids+i,nullptr,ThreadRoutine,p);  
    
    
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

当我们用exit退出时发现整个进程都退出了,所以不能使用exit终止线程

pthread_self

每个线程可以获得自己的线程id:pthread_self

void* ThreadRoutine(void *args)

    //暂时的方案
    int i = *(int*)args;
    delete (int*)args;
    int cnt = 0;
    while(cnt<5)
    
        cout<<"thread index : "<< i <<"count:"<<cnt<<"thread id: "<<pthread_self()<<endl;
        sleep(1);
        cnt++;
    
    cout<<"thread: "<<i<<"quit"<<endl;
    //exit(0);
    return nullptr;//线程退出:从自己的例程中return就代表线程退出

int main()

#define NUM 5
    pthread_t tids[NUM];
    for(auto i = 0;i<NUM;i++)
    
        int *p = new int(i);
    	pthread_create(tids+i,nullptr,ThreadRoutine,p);  
        cout<< "create thread: "<<tid[s]<<"success"<<endl;
    
    
    while(true)
    
        cout<<"main thread is running..."<<"main thread id:"<<pthread_self()<<endl;
        sleep(1);
    
    return 0;

pthread_exit线程终止

#include<iostream>
using namespace std;
#include<cstdio>
#include<pthread.h>
#include<unistd.h>
//void*是系统层面设计的一个通用接口
void* ThreadRoutine(void *args)

    //暂时的方案
    int i = *(int*)args;
    delete (int*)args;
    int cnt = 0;
    while(cnt<5)
    
        cout<<"thread index : "<< i <<"count"<<cnt<<endl;
        sleep(1);
        cnt++;
    
    cout<<"thread: "<<i<<"quit"<<endl;
    pthread_exit((void*)10);
    //exit(0);
    //return nullptr;//线程退出:从自己的例程中return就代表线程退出

int main()

#define NUM 5
    pthread_t tids[NUM];
    for(auto i = 0;i<NUM;i++)
    
        int *p = new int(i);
    	pthread_create(tids+i,nullptr,ThreadRoutine,p);  
    
    
    while(true)
    
        cout<<"main thread is running..."<<endl;
        sleep(1);
    
    return 0;

pthread_cancel

取消线程

void* ThreadRoutine(void *args)

    //暂时的方案
    int i = *(int*)args;
    delete (int*)args;
    int cnt = 0;
    while(true)//死循环执行
    
        cout<<"thread index : "<< i <<"count:"<<cnt<<"thread id: "<<pthread_self()<<endl;
        sleep(1);
        cnt++;
    
    cout<<"thread: "<<i<<"quit"<<endl;
    return nullptr;//线程退出:从自己的例程中return就代表线程退出

int main()

#define NUM 5
    pthread_t tids[NUM];
    for(auto i = 0;i<NUM;i++)
    
        int *p = new int(i);
    	pthread_create(tids+i,nullptr,ThreadRoutine,p);  
        cout<< "create thread: "<<tids[i]<<"success"<<endl;
    
    sleep(5);
    for(auto i = 0;i<NUM;i++)
    
        pthread_cancel(tids[i]);//取消线程
        cout<<"thread : "<<tids[i] << "been cancel"<<endl;
        sleep(1);
    
    
    while(true)
    
        cout<<"main thread is running..."<<"main 

以上是关于Linux线程基础详解的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核线程kernel thread详解--Linux进程的管理与调度

linux线程详解:线程概念线程调度线程安全线程模型

web服务处理过程,各种I/O模型详解,

Linux线程基础详解

Linux线程基础详解

python 多线程详解