Linux入门之多线程|线程|进程基本概念及库函数

这篇具有很好参考价值的文章主要介绍了Linux入门之多线程|线程|进程基本概念及库函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux入门之多线程|线程|进程基本概念及库函数,jvm


目录

一、线程

1.线程的概念

补充知识点:页表

2.线程的优点

3.线程的缺点

4.线程异常

5.线程用途

二、线程与进程的区别与联系

三、关于进程线程的问题

0.posix线程库

1.创建线程

关于pthread_create的后两个参数

1.传入指针

2.传入对象

2.线程终止

3.取消线程

4.线程等待(等待线程结束)

5.线程分离

1.线程库

2.线程id

3.线程分离

四、C++11中的使用线程(语言级使用)


一、线程

1.线程的概念

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列
  • 一切进程至少有一个线程
  • 线程在进程内部运行,本质是在进程的地址空间内运行
  • 在linux系统中,从cpu看到,进程包括进程pcb,地址空间,页表等。看到的线程只有pcb
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成线程执行流

        线程是一个执行分支,执行粒度比进程更细,调度成本更低,这里的调度成本就是不用切换pcb,切换cache等

        从内核角度来看,线程是cpu调度的基本单元,进程是承担分配系统资源的基本实体

Linux入门之多线程|线程|进程基本概念及库函数,jvm

  • 对于cpu来说,无法区分进程/线程,cpu内部包含:运算器,控制器,寄存器,MMU,硬件cache(高速缓存)l1,l2,l3。
  • 对于一个进程来说,里面可能存在多个线程,os需要对这些线程管理,创建一个线程控制块(TCB),TCB属于PCB,在windows中确实是这样干的,但是在linux中,用pcb来模拟tcb,复用了进程的代码和结构,更好维护,效率更高,这也就解释了为什么linux可以不间断的运行。在实际运行中,os系统使用最频繁的功能除了os本身,接下来就是进程。linux没有真正意义上的线程。
  • 进程在调度的时候,用pid来识别,线程调度的时候,用lwp来识别。

补充知识点:页表

前面有写过页表是kv结构的,里面包含虚拟地址以及映射到真实物理内存的地址,还包括其他属性。

物理内存不是按字节划分的,如果按字节划分,频繁的操作io,注定过多的寻址,就会导致过多的机械运动,所以效率十分低下,所以os和磁盘设备进行交互的时候,是按块为单位。所以物理内存是按块划分的(4KB),每个块称为页page。内存管理的本质就是将磁盘中特定的4KB块(数据内容)放到一个物理内存的4KB空间。os对物理内存也有自己的管理方式,里面存储每块的属性。

为什么要以块为单位?

1.文件系统+编译器 文件在磁盘的时候,就是以块为单位(4KB)

2.os+内存:内存在实际进行内存管理,也是以4kb为单位

3.局部性原理的特性:允许我们提前加载正在访问数据的相邻或者附近的数据

我们会通过预先加载要访问的数据的附近数据来减少未来io的次数,多加载进来的数据本质上就叫做数据的预加载。

为什么要以4KB为单位

1.IO的基本单位(内核内存+文件系统)

2.通过局部性原理 预测未来的命中情况 提高效率

虚拟地址空间有32位,难道页表需要2^32byte吗?NO

实际上虚拟地址是按照10+10+12来划分的,首先高10位,找到第一级页表(页目录),kv找到第二级的页表(中间10位地址在这个页表内kv)找到物理内存对应页框的起始地址,最后用虚拟地址的后12位对应的数据地址找到页内偏移,即采用基地址+偏移量的方法,在物理内存中定位任意一个内存字节的位置

Linux入门之多线程|线程|进程基本概念及库函数,jvm

  • 在实际申请malloc的时候,os只在虚拟地址空间上申请就行了,在真正访问的时候,进程发现了这个kv关系不存在,触发缺页中断,os才会去申请或者填充页表,并申请具体的物理内存。
  • 看下面一段代码:
char *s  = "hello";
*s = 'h';

s是一个指针,指向字符常量区,现在对s进行修改,在语言层面上,会中断进程。

从内核角度:s里保存的是指向字符的虚拟起始地址,*s进行寻址的时候,需要进行虚拟地址向物理地址进行转换,使用MMU+页表的方式,此时os对进程的操作进行审查,虽然可以找到这个地址,但是操作是非法的,此时mmu异常,os识别异常转换为信号,发送给目标进程,目标进程在从内核态转为用户态的时候,进行信号处理,终止进程。

2.线程的优点

  • 创建一个新的线程比新的进程代价要小很多
  • 与进程切换相比,切换线程的工作os要做的工作少很多
  • 线程占用的资源比进程少很多
  • 能够充分利用多处理器的可并行数量
  • 在等待慢速I/O结束的同时,程序可以执行其他的计算任务
  • 计算密集型应用,如,加密解密,文件压缩解压等,可以分解到多个线程实现。
  • IO密集型应用,如上传下载,需要等待磁盘和网络带宽,线程可以同时等待不同的IO操作。

3.线程的缺点

  • 性能损失   一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了,不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制  进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

4.线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

5.线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

二、线程与进程的区别与联系

  • 线程共享进程数据,但是也拥有自己的一部分数据:线程id,一组寄存器,errno,信号屏蔽字,调度优先级
  • 线程共享以下进程的资源和环境:文件描述符表fd,每种信号的处理方式(默认或者自定义),当前工作目录,用户id和组id

三、关于进程线程的问题

之前学习的单进程,就可以看成是具有一个线程执行流的进程

0.posix线程库

  • 与线程有关的函数pthread_t
  • 使用库函数,引入头文件<pthread.h>
  • makefile里链接这些线程函数库要使用编译器命令的 -lpthread选项

1.创建线程

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

参数:
thread:返回线程的ID
attr:设置线程属性,一般null为默认属性
start_routine:函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数

返回值:成功返回0,失败返回错误码

关于pthread_create的最后一个参数

这个参数为线程要执行任务的参数,类型为(void * ),根据传入参数的不同,执行不同的方法

1.传入指针

//void * threadRun(void * args)
// {
//     const char * name = (const char *)args;
//     int cnt = 5;
//     while(cnt)
//     {
//         cout<<name<<"is running"<< cnt --<<endl;
//         sleep(1);
//     }
//     pthread_exit((void*)11);
// }
// int main()
// {
//     pthread_t tid;
//     pthread_create(&tid,nullptr,threadRun,(void *)"thread 1");
//     void *ret = nullptr;
//     pthread_join(tid,&ret);

//     //linux下int*是几个字节
//     cout<<"new_thread exit "<<(int64_t)ret<<endl;
//     return 0;
// }

2.传入对象

enum
{
    OK = 0,
    ERROR;
};
//首先定义一个对象
class ThreadData
{
    public:
        //构造
        ThreadData(const string & name,int id,time_t createtime,int top)
        :_name(name),_id(id),_createTime((uint64_t)createtime),_status(OK),_top(top)
        {}
    
        //析构
        ~ThreadData
        {
        }

    public:  
        //成员变量
        string _name;
        int _id;
        uint64_t _createTime;
        //返回的数据
        int _status;
        int _top;
        int _result;
 };

void * thread_run(void * args)
{
    ThreadData *td = static_cast<ThreadData*>(args);
    
    //执行要做的
    for(int i = 0; i<td->_top;i++)
    {
        td->_result += i;
    }

    cout<<td->_name<<"cal done"<<endl;
    pthread_exit(td);
}

int main()
{
    pthread_t tids[NUM];
    for(int i = 0; i<NUM; i++)
    {
        char tneme[64];
        snprintf(tname,64,"thread -%d", i+1);
        ThreadData * td = new ThreadData(tname,i+1,time(nullptr),5*i); //构建对象
        pthread_create(tid+i,nullptr,thread_run,td);
    }


    //等待所有线程
    for(int i = 0; i<NUM;i++)
    {
        int n = pthread_join(tids[i],&ret);
        if(n!= 0) cerr<<"pthread jion err"<<endl;
        Thread td = static_cast<Thread_Data *> (ret);
        if(td->_status == OK)
            cout<<"result is :"<<td->_result<<endl;
    }

    return 0;
}

2.线程终止

线程终止有3种方法:

  1. 从线程函数return ,这种方法对主线程不适用,从main函数里return相当于exit
  2. 线程调用pthread_exit终止自己
  3. 一个线程可以调用pthread_cancel终止同一个进程中的另一个线程
void pthread_exit(void * value_ptr);
参数:
value_ptr不要指向一个局部变量
返回值:
无返回值,跟进程相同,线程结束的时候无法返回到它的调用者

注意:pthread_exit或者return返回的指针所指向的内存单元必须是全局的,或者malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了

3.取消线程

int pthread_cancel(pthread_t thread);
//thread:线程id
//成功返回0,失败返回错误码

4.线程等待(等待线程结束)

  • 已经退出的线程,其空间没有被释放,仍在进程的地址空间内(类似于僵尸进程)
  • 创建新的线程不会复用刚才退出线程的地址空间
int pthread_join(pthread_t thread,void ** value_ptr);
//thread 线程Id
//value_ptr:指向一个指针, void* * ,指向线程的返回值
//返回值:成功0,失败返回错误码

调用该函数的线程将挂起等待,知道id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态不同

  1. 线程return返回,value_ptr指向的单元存放的是线程函数的返回值
  2. pthread_cancel异常终止,存放PTHREA_CANCLED
  3. 自己调用pthread_exit终止,存放pthread_exit()中的参数
  4. 对thread终止状态不感兴趣,可以传递null

5.线程分离

1.线程库

可以在路径/lib64/libthread-2.17.so中看到,线程就是一个动态库。就注定了它加载到内存,最后被页表映射进虚拟地址空间中的共享区。进程中的线程可以随时访问库中的代码和数据,所以我们要对线程进行管理,使用TCB(thread control block)

2.线程id

前面有介绍文件描述符,fd是struct file中的下标。在pthread.h中也定义了线程的各种属性,里面可能有struct_pthread,线程栈,线程的局部存储等。均在TCB中。这时,线程id,也就是TCB的起始地址,用来标识线程相关属性。

线程栈是一个私有栈,主线程使用的栈为公有栈,在内存空间系统栈。新线程提供的是库中的栈。

Linux入门之多线程|线程|进程基本概念及库函数,jvm

3.线程分离

默认情况下,新创建的线程是joinable的,线程退出侯,需要对其作pthread_join操作,否则无法释放资源,从而导致系统内存泄漏。

如果不关心线程的返回值,join是一种负担,这个时候可以告诉系统,当线程退出时,自动释放线程资源。

int pthread_detach(pthread_t pthread);

可以是线程组内其他线程对目标线程分离,也可以是线程自己分离

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是jionable又是分离的。否则会导致invalid arguments文章来源地址https://www.toymoban.com/news/detail-693916.html

四、C++11中的使用线程(语言级使用)

#include<thread>


void run1()
{
    while(true)
    {
        cout<<"thread 1"<<endl;
        sleep(1);
    }

}

void run2()
{
    while(true)
    {
        cout<<"thread 2"<<endl;
        sleep(2);
    }
}


int main()
{
    thread th1(run1);
    thread th2(run2);

    th1.join();
    th2.join();
    
    return 0;
}

到了这里,关于Linux入门之多线程|线程|进程基本概念及库函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux从入门到精通】进程的基本概念

        我们通过对上篇文章冯诺依曼体系结构对硬件进行讲解后, 本篇文章会对进程进行深入讲解。同时会讲解PCB(进程控制块)。希望本篇文章内容会对你有所帮助。 文章目录 一、再次理解操作系统 1、1 操作系统的作用 1、2 操作系统的管理 二、进程基本的概念 2、1 什么是

    2024年02月05日
    浏览(48)
  • 【TCP/IP】多进程服务器的实现(进阶) - 进程的概念及fork函数

    目录 进程的概念及应用 进程的定义 进程的ID fork函数(进程创建函数)         多进程(以及多线程)是现代计算机网络的精髓。在之前,我们所做的诸如回声服务器、回声客户端、文件收发等都是偏向基础的单进程应用。而经过前面的铺垫,我们对Socket也有了一定了解

    2024年02月09日
    浏览(56)
  • 从零开始学习 Java:简单易懂的入门指南之多线程(三十四)

    1.1简单了解多线程 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。 1.2并发和并行 并行:在同一时刻,有多个指令在多个CPU上同时执行。 并发:在同一时刻,有多个指令在单个CPU上交

    2024年02月08日
    浏览(67)
  • 线程和进程的区别(从JVM角度出发)

    线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。 根本区别:进程是操作系

    2024年01月22日
    浏览(92)
  • 【Python从入门到进阶】34、selenium基本概念及安装流程

    接上篇《33、使用bs4获取星巴克产品信息》 上一篇我们介绍了如何使用bs4来解析星巴克网站,获取其产品信息。本篇我们来了解selenium技术的基础。 Selenium是一种用于自动化Web浏览器操作的开源工具。它提供了一组API(应用程序接口),使开发人员能够使用多种编程语言(如

    2024年02月10日
    浏览(35)
  • Linux 多线程( 进程VS线程 | 线程控制 )

    进程是资源分配的基本单位。 线程是OS调度的基本单位。 线程共享进程数据,但也拥有自己的一部分数据: 线程ID 一组寄存器 ,用来保存每个线程的上下文数据,让每个线程能够合理调度。 栈 ,每个线程入栈出栈产生的临时变量必须保存到每个线程的私有栈中,所以栈对于

    2024年02月07日
    浏览(44)
  • c语言系统编程之多进程

    程序与进程的区别? 程序是静态的未运行的二进制文件,存储在磁盘中 进程是已经运行的二进制文件,存储在内存中 进程的内存划分图有哪几部分? 堆(存储malloc和calloc出来的空间)、栈(局部变量、环境变量、命令行参数)、数据段(全局变量、静态变量、常量)、代码

    2024年02月07日
    浏览(39)
  • IO进程线程第五天(8.2)进程函数+XMind(守护进程(幽灵进程),输出一个时钟,终端输入quit时退出时钟)

    1.守护进程(幽灵进程) 2.输出一个时钟,终端输入quit时退出时钟        

    2024年02月14日
    浏览(48)
  • JavaEE之多线程编程:4. 线程安全(重点!!!)

    下面我们来举个例子: 我们大家都知道,在单线程中,以下的代码100%是正确的。 但是,两个线程,并发的进行上述循环,此时逻辑可能就出现问题了。 上述这样的情况就是非常典型的线程安全问题。这种情况就是bug!! 只要实际结果和预期的结果不符合,就一定是bug。 想

    2024年01月25日
    浏览(41)
  • Linux--进程多线程(上)

            精神内耗一方面可能是消极的,人好像一直在跟自己过不去,但其实它也是一种积极的情绪。精神内耗在某种程度上,是在寻找一种出口,寻找他自己人生的出口,寻找我今天的出口,或者寻找我一觉醒来明天的出口。我们从积极的角度谈论的话,精神内耗不是一个

    2023年04月17日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包