• 5、进程线程模型你知道多少?


    对于进程和线程的理解和把握可以说基本奠定了对系统的认知和把控能力。其核心意义绝不仅仅是“线 程是调度的基本单位,进程是资源分配的基本单位”这么简单。

    多线程

    我们这里讨论的是用户态的多线程模型,同一个进程内部有多个线程,所有的线程共享同一个进程的内存空间,进程中定义的全局变量会被所有的线程共享,比如有全局变量int i = 10,这一进程中所有并发运行的线程都可以读取和修改这个 i 的值,而多个线程被CPU调度的顺序又是不可控的,所以对临界资源的访问尤其需要注意安全。

    我们必须知道,做一次简单的i = i + 1在计算机中并不是原子操作,涉及内存取数,计算和写入内存几个环节,而线程的切换有可能发生在上述任何一个环节中间,所以不同的操作顺序很有可能带来意想不到的结果。

    但是,虽然线程在安全性方面会引入许多新挑战,但是线程带来的好处也是有目共睹的。首先,原先顺序执行的程序(暂时不考虑多进程)可以被拆分成几个独立的逻辑流,这些逻辑流可以独立完成一些任务(最好这些任务是不相关的)。

    比如 QQ 可以一个线程处理聊天一个线程处理上传文件,两个线程互不干涉,在用户看来是同步在执行两个任务,试想如果线性完成这个任务的话,在数据传输完成之前用户聊天被一直阻塞会是多么尴尬的情况。

    对于线程,我认为弄清以下两点非常重要:

    1.线程之间有无先后访问顺序(线程依赖关系)

    2.多个线程共享访问同一变量(同步互斥问题)

    另外,我们通常只会去说同一进程的多个线程共享进程的资源,但是每个线程特有的部分却很少提及, 除了标识线程的tid,每个线程还有自己独立的栈空间,线程彼此之间是无法访问其他线程栈上内容的。

    而作为处理机调度的最小单位,线程调度只需要保存线程栈、寄存器数据和PC即可,相比进程切换开销要小很多。

    线程相关接口不少,主要需要了解各个参数意义和返回值意义。

    1. 线程创建和结束

    背景知识:

      在一个文件内的多个函数通常都是按照main函数中出现的顺序来执行,但是在分时系统下,我们 可以让每个函数都作为一个逻辑流并发执行,最简单的方式就是采用多线程策略。在main函数中 调用多线程接口创建线程,每个线程对应特定的函数(操作),这样就可以不按照main函数中各 个函数出现的顺序来执行,避免了忙等的情况。线程基本操作的接口如下。

    相关接口:

      创建线程:int pthread_create(pthread_t *pthread, const pthread_attr_t *attr, void (start_routine)(void *), void *agr);

      创建一个新线程,pthread和start_routine不可或缺,分别用于标识线程和执行体入口,其他可以 填NULL。

        pthread:用来返回线程的tid,*pthread值即为tid,类型pthread_t == unsigned long int。

        attr:指向线程属性结构体的指针,用于改变所创线程的属性,填NULL使用默认值。

        start_routine:线程执行函数的首地址,传入函数指针。

        arg:通过地址传递来传递函数参数,这里是无符号类型指针,可以传任意类型变量的地址, 在被传入函数中先强制类型转换成所需类型即可。

      获得线程ID:pthread_t pthread_self();

      调用时,会打印线程ID。

      等待线程结束:int pthread_join(pthread_t tid, void** retval);

        主线程调用,等待子线程退出并回收其资源,类似于进程中wait/waitpid回收僵尸进程,调用 pthread_join的线程会被阻塞。

        tid:创建线程时通过指针得到tid值。

        retval:指向返回值的指针。

      结束线程:pthread_exit(void *retval);

        子线程执行,用来结束当前线程并通过retval传递返回值,该返回值可通过pthread_join获得。

        retval:同上。

      分离线程:int pthread_detach(pthread_t tid);

        主 线 程 、 子 线 程 均 可 调 用 。 主 线 程 中 pthread_detach(tid) , 子 线 程 中 pthread_detach(pthread_self()),调用后和主线程分离,子线程结束时自己立即回收资源。

        tid:同上。

    2. 线程属性值修改

    背景知识:

    线程属性对象类型为pthread_attr_t,结构体定义如下:

     相关接口:

    对上述结构体中各参数大多有:pthread_attr_get()和pthread_attr_set()系统调用函数来设置和获取。这 里不一一罗列。

    多进程

    每一个进程是资源分配的基本单位。

    进程结构由以下几个部分组成:代码段、堆栈段、数据段。代码段是静态的二进制代码,多个程序可以共享。

    实际上在父进程创建子进程之后,父、子进程除了pid外,几乎所有的部分几乎一样。

    父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。

    如果子进程想要运行自己的代码段,还可以通过调用execv()函数重新加载新的代码段,之后就和父进程独立开了。

    我们在shell中执行程序就是通过shell进程先fork()一个子进程再通过execv()重新加载新的代码段的过程。

    1. 进程创建与结束

    背景知识: 进程有两种创建方式,一种是操作系统创建的一种是父进程创建的。从计算机启动到终端执行程 序的过程为:0号进程 -> 1号内核进程 -> 1号用户进程(init进程) -> getty进程 -> shell进程 -> 命令行 执行进程。所以我们在命令行中通过 ./program执行可执行文件时,所有创建的进程都是shell进程 的子进程,这也就是为什么shell一关闭,在shell中执行的进程都自动被关闭的原因。从shell进程到 创建其他子进程需要通过以下接口。

    相关接口:

    创建进程:pid_t fork(void);

      返回值:出错返回-1;父进程中返回pid > 0;子进程中pid == 0

    结束进程:void exit(int status);

      status是退出状态,保存在全局变量中S?,通常0表示正常退出。

    获得PID:pid_t getpid(void);

      返回调用者pid。

    获得父进程PID:pid_t getppid(void)

      返回父进程pid。

    其他补充:

    1.正常退出方式:exit()、_exit()、return(在main中)。

      exit()和exit()区别:exit()是对_exit()的封装,都会终止进程并做相关收尾工作,最主要的区别是 _exit()函数关闭全部描述符和清理函数后不会刷新流,但是exit()会在调用_exit()函数前刷新数据流。

      return和exit()区别:exit()是函数,但有参数,执行完之后控制权交给系统。return若是在调用函数中,执行完之后控制权交给调用进程,若是在main函数中,控制权交给系统。

    2.异常退出方式:abort()、终止信号。

    2. Linux进程控制

    进程地址空间(地址空间)

    虚拟存储器为每个进程提供了独占系统地址空间的假象。

    尽管每个进程地址空间内容不尽相同,但是他们的都有相似的结构。X86 Linux进程的地址空间底部 是保留给用户程序的,包括文本、数据、堆、栈等,其中文本区和数据区是通过存储器映射方式将磁 盘中可执行文件的相应段映射至虚拟存储器地址空间中。

    有一些"敏感"的地址需要注意下,对于32位进程来说,代码段从0x08048000开始。从0xC0000000开始 到0xFFFFFFFF是内核地址空间,通常情况下代码运行在用户态(使用0x00000000 ~ 0xC00000000的 用户地址空间),当发生系统调用、进程切换等操作时CPU控制寄存器设置模式位,进入内和模式, 在该状态(超级用户模式)下进程可以访问全部存储器位置和执行全部指令。

    也就说32位进程的地址空间都是4G,但用户态下只能访问低3G的地址空间,若要访问3G ~ 4G的地址 空间则只有进入内核态才行。

    进程控制块(处理机)

    进程的调度实际就是内核选择相应的进程控制块,被选择的进程控制块中包含了一个进程基本的信息。

    上下文切换

    内核管理所有进程控制块,而进程控制块记录了进程全部状态信息。每一次进程调度就是一次上下文 切换,所谓的上下文本质上就是当前运行状态,主要包括通用寄存器、浮点寄存器、状态寄存器、程 序计数器、用户栈和内核数据结构(页表、进程表、文件表)等。

    进程执行时刻,内核可以决定抢占当前进程并开始新的进程,这个过程由内核调度器完成,当调度器 选择了某个进程时称为该进程被调度,该过程通过上下文切换来改变当前状态。

    一次完整的上下文切换通常是进程原先运行于用户态,之后因系统调用或时间片到切换到内核态执行 内核指令,完成上下文切换后回到用户态,此时已经切换到进程B。

  • 相关阅读:
    KubeEdge的云边协同设计原理
    mxnet的broadcast_power() 注释错误
    IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
    pytorch CrossEntropyLoss() 默认转换onehot编码
    SpringBoot 启动时,报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ddl.dealcars.mapper.UserMapper.update
    SpringBoot搭建时,报 Cannot resolve symbol 'SpringApplication
    SpringBoot+vue 解决跨域问题
    SpringBoot集成 Swagger 后,启动报 org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
    test
    438. 找到字符串中所有字母异位词
  • 原文地址:https://www.cnblogs.com/crbhf/p/15130511.html
Copyright © 2020-2023  润新知