文章目录
- 多线程
- (多线程问题的)三大源头
- 两个主要问题
- 两大解决方案
- 高并发问题解决方案
对多线程和高并发相关问题整理了一个简单的提纲。
通过这个提纲,足够引出对并发编程中大部分问题的讨论~
多线程
(多线程问题的)三大源头
线程并发执行带来的原子性问题。这是最主要的问题,无论是单核场景下的时间片轮转(线程切换),还是多核场景下的并行,都可能会出现多线程对同一个共享变量进行并发访问的情况,而这个访问过程可能涉及到多个步骤,并不是原子性的,这将产生竞态条件或导致数据竞争。
CPU缓存带来的可见性问题:程序员要编写符合Happens Before
(可见性规则)的代码保证可见性。
大多数情况不需要担心这个问题,但如果出现了不可见的情况(比如"咦,这个修改怎么没生效?"),要想到可能是这个原因导致的。
在C/C++/Java中,可以使用volatile
关键字来修饰变量,使内存中的共享变量一旦修改完毕,就会立即将该变量刷新回主内存。
编译器带来的有序性问题:编译器的优化有可能会将指令重排。这种问题通常会出现在涉及到共享数据的多线程编程中,因为编译器的优化可能会导致操作的重新排序,从而破坏了程序的预期行为,例如导致可见性问题。
两个主要问题
(线程切换带来的原子性问题,会最终表现为以下两个问题)
竞态条件。强调并发执行顺序带来的结果不一致问题。
常见的竞态条件类型:先判断后执行竞态条件、读-操作-更新竞态条件(如i++
)。
数据竞争。强调并发读写导致的未定义行为,可能会引起程序的崩溃。
数据竞争的类型:读写竞争、写写竞争。
两大解决方案
同步机制:利用原子操作或锁保证同步。缺点是大量使用锁会让很多逻辑都变成同步,效率可能还不如单线程。
- 原子操作是无锁同步机制,但仅适用于单值操作。
- 锁适用范围广。除了常用的原生锁,分布式场景下还可能需要用到分布式锁。
数据分片(actor模型):将数据分为多个部分,由不同的线程独立处理。具体思路是根据数据所有者划分出数据主体(即actor),每个actor在自己对应的线程中处理/访问自己的数据。
高并发问题解决方案
- 多线程:充分利用CPU性能,提高系统的性能和吞吐量。
- 负载均衡: 使用负载均衡器将请求分发到多个服务器上,以平衡系统的负载。
- 分布式架构:将系统拆分为多个独立的服务,采用分布式架构,可以减轻单一服务的压力,并提高整体的并发处理能力。
- 缓存:利用缓存技术来减轻数据库和其他服务的压力。将频繁访问的数据缓存起来,以减少对底层存储系统的访问。
- 异步处理:可以利用消息队列对消息进行异步解耦,避免大量消息等待响应。
- 其它:水平扩展、数据库优化等。