目录
线程间通信
典型问题:
- 请简述互斥锁与自旋锁的区别
- 线程间有哪些同步的方式
线程间通信常见的方法
- 互斥锁(mutex)
- 读写锁(rwlock)
- 自旋锁(spin_lock)
- 条件变量(condition)
互斥锁(mutex)
- 属于sleep-waiting类型的锁
- 能保护临界资源
- 当一个线程访问临界资源时,互斥锁会拒绝另外一个线程访问临界资源
- 如之前提到的读者-写者问题
- 可通过互斥锁来保证原子性,实现互斥访问
- 互斥量(互斥锁)是最简单的线程同步的方法
- 互斥量,处于两态之一的变量:解锁和加锁
- 两个状态可以保证资源访问的串行
- 操作系统层和高级语言都直接提供了接口,可直接使用
自旋锁(spin_lock)
- 属于busy-waiting类型的锁
- 能保护临界资源
- 当一个线程访问临界资源时,互斥锁会拒绝另外一个线程访问临界资源
- 但自旋锁实现原理与互斥锁实现原理不一样
- 自旋锁也是一种多线程同步的方法
- 使用自旋锁的线程会反复检查锁变量是否可用
- 自旋锁不会让出CPU,是一种忙等待状态,进行忙等待并不停的进行锁请求,直到得到这个锁为止
- 自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低
- 自旋锁避免了进程或线程上下文切换的开销
- 操作系统内部很多地方使用的是自旋锁
- 自旋锁不适合在单核CPU使用
互斥锁与自旋锁对比
- 最大区别:
- 互斥锁在等待时会让出CPU,会被阻塞
- 而自旋锁则不会,会忙等待
- 互斥锁mutex:独占锁;开销大
- 自旋锁spin_lock:轻量级的锁,开销小;适用于短时间内对锁的使用
- 因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁
- 注意:对于spin_lock,如果递归调用过深,会导致死锁
读写锁(rwlock)
- 有这样一种情况:
- 临界资源多读少写
- 读取的时候并不会改变临界资源的值
- 这就存在效率更高的同步方法,即读写锁
- 读写锁是一种特殊的自旋锁
- 允许多个读者同时访问资源以提高读性能
- 对于写操作则是互斥的
条件变量(condition)
- 条件变量是一种相对复杂的线程同步方法
- 条件变量允许线程睡眠,直到满足某种条件
- 当满足条件时,可以向该线程发出信号,通知唤醒
- 以生产者消费者模式来看:
- 缓冲区小于等于0时,不允许消费者消费,消费者必须等待
- 当生产者生产一个产品时,唤醒可能等待的消费者
- 缓冲区满时,不允许生产者往缓冲区生产,生产者必须等待
- 当消费者消费一个产品时,唤醒可能等待的生产者
- 这2种唤醒操作正是通过条件变量所实现的
- 条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用
- 条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定
- 而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用
- 两个线程利用条件变量及互斥锁实现同步
- 一个线程利用条件变量实现等待,同时释放锁;
- 一个线程获取锁后利用该条件变量唤醒等待的线程
进程间通信
典型问题:
- 进程间有哪些同步方式
- 请简述对进程间通信的管道的理解
回顾:进程vs线程
- 线程是系统进行运行调度的最小单位
- 进程是系统进行资源分配和调度的基本单位
- 从这也可以看出之前线程间通信的方法对进程并不适用
进程间通信的方法
- 管道
- 消息队列
- 共享内存
- 信号
- 套接字
管道(pipe)
- 内核提供,单工,自同步机制
- 以Linux一命令举例
- cat server.log | grep ERROR | grep Thread
- 这里的 "|" 实际上就是管道的意思
- "|" 前面部分作为 "|" 后面的输入
- 上面的是匿名管道
- 可以通过mkfifo命令来创建命名管道
- 自同步机制:迁就慢的那一方
- 两个进程在使用管道通信时,一端为读端,一端为写端
- 假设读端很快,写端速度很慢,那么读端很快就会将管道给读空,如果当前管道读空,但是写端仍然存在的话,作为读端,你要一直在那等待,等到写端写数据到管道
消息队列
- 是存储在内核中的一个消息的队列(链表)
- 能从一个进程向另外一个进程发送一个带有类型的数据块
- 遵循先进先出的原则,保证了时间的顺序性
- 支持双向传输,可以使用消息类型区分不同的消息
- 消息作为节点一个一个地存放在消息队列里,可把消息队列比作信箱,消息比作依次顺序存放的信件
- 地址比作消息类型,内容为消息
共享内存
- 为何用共享内存
- 每个进程都有自己的进程空间
- 进程空间通过页表、通过段页式存储管理与实际的物理内存建立起映射
- 因此,在某种程度上,多进程是共同使用物理内存的
- 但是由于操作系统的进程管理,进程间的内存空间是独立的;也就是进程1,2它们逻辑上的内存空间是完全没有联系的,保证了每个进程独立运行时的安全性
- 进程之间,进程空间互不干扰,相互独立
- 进程默认是不能访问进程空间之外的内存空间的,也就是说一个进程不能访问另外一个进程的进程空间
- 而共享内存则能打破这个限制
- 通过共享内存,进程就可以通过页表来映射到同样的一个内存里去
- 这个内存既可以被进程1使用,也能被进程2使用,2者都能读写
- 通过共享内存,进程之间就建立了联系
- 共享内存简要
- 共享存储允许不相关的进程访问同一片物理内存
- 共享内存是两个进程之间共享和传递数据最快的方式
- 共享内存未提供同步机制,需要借助其它机制管理访问
- 共享内存是高性能后台开发中最常用的进程同步方式
- 使用共享内存步骤
- 1.申请共享内存
- 2.连接到进程空间
- 3.使用共享内存
- 4.脱离进程空间&删除
信号
- 信号是事件发生时对进程的通知机制,有时也叫软件中断,信号可以让一个正运行进程被另一个运行进程异步进程中断,转而处理某突发事件
- 信号是进程间通信唯一的异步方式,是对中断的一种软件模拟
- 在操作系统中,不同的信号使用不同的值来表示
- 接收信号的进程需要注册对应的信号处理函数
- 走Linux中可以使用kill -l
- 查看支持的信号列表(不列举了)
网络套接字
- 网络层:提供主机之间的通信
- 传输层:提供主机不同进程之间的通信
- 应用层:提供不同应用之间的通信
- 主机通过IP地址来标识
- 主机网络进程通过端口来标识
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程
- 一个进程可以绑定多个端口号,但是一个端口号只能被一个进程占用
- socket是一种操作系统提供的进程间通信机制
- 在操作系统中,通常会为应用程序提供一组应用程序接口(API),称为套接字接口(英语:socket API)
- 应用程序可以通过套接字接口,来使用网络套接字,以进行资料交换
- 在套接字接口中,以IP地址及端口组成套接字地址(socket address)
- 远程的套接字地址,以及本地的套接字地址完成连线后,再加上使用的协议(protocol),这个五元组(five-element tuple),作为套接字对(socket pairs),之后就可以彼此交换资料
- 操作系统根据套接字地址,可以决定应该将资料送达特定的行程或线程
- 这就像是电话系统中,以电话号码加上分机号码,来决定通话对象一般
Unix域套接字
- 为本机进程间通信而生
- 域套接字是一种高级的进程间通信的方法
- Unix域套接字可以用于同一机器进程间通信
- Unix系统提供的域套接字提供了网络套接字类似的功能
- 共享内存需要额外的同步机制来同步多个进程间的通信
- Unix域套接字则不需要额外的机制来保证多个进程间访问的问题
- Unix域套接字通信无需经过完整的网络协议栈
- 实现本机进程间通信,“域套接字”的效率高于“网络套接字”
- UNIX域套接字用于同一台pc上运行的进程之间通信,它仅仅复制数据,不执行协议处理,不需要增加删除网络报头,无需计算校验和,不产生顺序号,无需发送确认报文
- 域套接字只能用来实现“本机进程间通信”,而且还是专业的“本机间通信”
- 服务端使用域套接字过程
- 1.创建套接字
- 2.绑定套接字
- 3.监听套接字(监听是否有请求进来)
- 4.接受&处理信息
- 客户端使用域套接字过程
- 1.创建套接字
- 2.连接套接字
- 3.发送信息
本文含有隐藏内容,请 开通VIP 后查看