进程线程的一些知识点

写在前面:以下都是在准备秋招时,发现自身对于线程/进程的理解太浅(虽然下面的理解也偏表面),于是查了一些资料以及结合自身理解所记录,且仅限于理论,结合实践还有漫漫长路…… 真正的理解势必需要结合操作系统的内核;若有描述不当还望指出;(不求有功但求无过…

进程与线程的区别

一个程序至少有一个进程,一个进程至少有一个线程

在支持线程的系统中,系统CPU调用的基本单位为线程

每个线程都拥有其独立的TCB(线程控制块),包含线程id、线程状态、优先级、栈指针等,但一个进程的所有线程,共享该进程的一段内存空间,也即一个线程崩溃,会导致整个进程崩溃,因此多进程程序比多线程健壮

引入线程,是因为进程间的交互、进程创建与销毁开销较大,而线程的创建与销毁则能够一定程度上提高效率

线程池

上面提到,线程的创建与销毁时间大大缩短,但对于目前的网络服务器而言,有大量的任务是需要短时间内处理的,若将线程的执行过程分为三块:

  • T1: 线程创建时间
  • T2: 任务执行时间
  • T3: 线程销毁时间

则对于单个任务,真正有效时间为 T 2 T 1 + T 2 + T 3 \frac{T_2}{T_1+T_2+T_3} T1+T2+T3T2

也即T1、T3是浪费的,那么对于请求频繁但执行时间较短的任务,相当一部分时间被浪费

于是引入了线程池

通俗的讲,线程池相当于在任务开始之前,先预创建了一部分线程置于队列中(消耗一小部分内存,不消耗CPU资源),此后只要传入一个任务,则调用一个空闲线程进行处理,任务执行完毕后不销毁,而是返回空闲队列等待下一次调用

若任务数大于初始创建的线程数,则继续新建一部分数量的线程。当所有任务执行完毕,系统检测到大部分线程在一定时间内处于空闲状态,则线程池自动销毁一部分线程,也即回收了那部分内存资源

线程池的概念,更适用于上述所说,请求频繁但执行时间短,若类似传输文件,整个任务的执行时间远大于创建销毁线程的时间,则线程池的作用也就没那么大
更深入的理解可参考深入解析C++编程中线程池的使用

多线程与多进程

并发:一段时间间隔内同时发生
并行:同一时刻同时发生

因此,无论是单核还是多核,都是支持并发,也即支持多线程/多进程
而对于单核,或单CPU的各线程间,目前一般采用时间片轮转抢占式的方法进行线程调度

多进程的优点:更鲁棒,一个进程的崩溃,不会引起整个系统崩溃
不同维度下多线程与多进程的对比:

对比维度多进程多线程总结
数据共享、同步数据共享复杂,需要用IPC;
数据是分开的,同步简单
因为共享进程数据,数据共享简单,因此导致同步复杂各有优势
内存、CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简单,CPU利用率高线程占优
创建销毁、切换创建销毁、切换复杂,速度慢创建销毁、切换简单,速度很快线程占优
编程、调试编程简单,调试简单编程复杂,调试复杂进程占优
可靠性进程间不会互相影响一个线程挂掉将导致整个进程挂掉进程占优
分布式适应于多核、多机分布式;
一台机器不够,扩展到多台机器简单
适应于多核分布式进程占优

细节与深入理解可参考
多线程与多进程的思考
多线程还是多进程的选择及区别

同步与异步

明确:

  1. 同步可以是单线程也可以是多线程;异步必须是多线程或多进程(每个进程可以是单线程)
  2. 回调可以是同步也可以是异步

理解:
同步相当于发送消息后,未收到反馈前不做任何处理;异步则是发送完消息无论对方是否有response,直接发送下一个消息
从编程语言上来理解,比如有两个函数a和b,其中b以函数指针的形式作为形参传递给a,那么:

  • 若a在调用了b函数后,等b执行完或返回了结果才继续执行,则为同步回调
  • 若a在调用b函数后,直接执行自身下面的功能,b也独立运行,则为异步回调

阻塞与非阻塞

在理解同步异步时,经常还能看到“阻塞”一词,明确:阻塞与非阻塞描述的是进程/线程的状态,一般是对于IO而言,其与同步异步是两个不同层面的概念

借用阻塞等于同步,非阻塞等于异步这种说法有什么错误?中的一个例子来理解:

有两个进程A和B,其中A又包含两个线程,其一为主线程a1,用于处理一些任务,另一个a2用于发送/接收信息

现在A中有abcdefg这七个任务,其中b和d为heavy task,因此A决定让B来做b和d这两个任务,而A自己做acefg这五个

于是首先A的a2发送b和d给B,并从此开始监听是否有信息从B发送回;当B处理完成并把结果返回,则A的主线程才能开始使用这个结果

在整个过程中,若A在处理完acefg五个任务后,仍旧未收到B的返回值,则A的两个线程均处于阻塞,a2要一直监听,直至收到B的结果,整个流程才能继续进行下去;

但是,A在发送任务b给B后,自己仍旧可以执行cefg,这里不需要等待B的结果,因此A和B之间是异步的;若A必须等B返回任务b的结果后,自己才能进行c任务,然后发送d给B,等d 的结果返回才能执行efg,则此时A和B是同步的。

进程间通信

  • 管道:具备先进先出性质的文件,当管道满时,写阻塞,管道空时,读阻塞
    普通管道可用于父子进程间的通信,位于内存中(环形缓冲区);命名管道则支持无亲缘关系间的通信,只要知道管道名即可,位于文件系统
  • 信号量:一个计数器,用于控制多个进程对共享资源的访问,常常作为一种锁机制,防止多个进程同时访问共享资源(同步)
  • 消息队列:消息的链表,通过消息队列标识符标识;克服了信号传递信息少、管道受缓冲区大小限制的问题
  • 共享内存:一段能被其它进程访问的内存,由一个进程创建,但多个进程都可以访问,常与其它通信机制如信号量配合使用,是最快的IPC方式
  • 套接字:用于不同主机间的进程通信

线程间通信

  • 锁机制:包括互斥锁、条件变量、读写锁
    (1) 互斥锁(互斥量):在访问共享资源前,先对互斥量加锁,这样其它试图访问该资源(申请锁)的线程都将被阻塞;在访问完成后释放互斥量,此时,所有阻塞的线程都变为就绪态,第一个申请到锁的线程将获取资源变为运行态,其它则再次阻塞(可能引起死锁
    (2) 条件变量:与互斥量配合使用,线程在改变条件变量之前须锁住互斥量,若不使用条件变量而仅使用互斥量,则会频繁地出现类似<加锁,判断,解锁>的流程,带来很多不必要的开销。这一点个人感觉稍微有点绕,理解可参考条件变量 之 稀里糊涂的锁
    (3) 读写锁:与互斥锁类似,但有更高的并行性。读写锁有三种状态:读锁、写锁和未加锁;一次只有一个线程可以申请到写锁(一旦申请到写锁,所有申请读/写的线程都将被阻塞),但同时可以有多个线程申请到读锁(申请写锁的进程将被阻塞,申请读锁的可以得到访问权)
  • 信号量
    同一进程的不同线程共享同一份内存区域,包括数据段、堆内存段等,因此线程间通信的目的主要用于线程同步,因此没有像进程通信中用于数据交换的通信机制

C++的常用函数可见linux基础——linux线程间通信及同步机制总结

附多线程的进程地址空间:
多线程的进程地址空间

最后还有一个微线程(协程),上述线程与进程的调度都是由CPU来实现,我们无法控制其何时切换;而微线程则是本质上的单线程,可以通过自己的代码来实现上下文切换,因此,需要在代码端明确允许进行切换的地方。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值