第三部分:并发与异步编程模型 —— 驾驭多核时代的力量
现代计算机多核处理器普及,充分利用并发和并行是提升程序性能的关键。但并发编程也带来了数据竞争、死锁、线程安全等复杂问题。深度掌握语言的并发模型,能够安全高效地编写并发程序。
3.1 并发的基本概念:并行 vs 并发,线程 vs 协程
并发:多个任务在逻辑上同时执行(交替进行)。
并行:多个任务在物理上同时执行(需要多核)。
线程:操作系统调度的最小单位,切换成本较高(几微秒到几十微秒)。
协程:用户态轻量级线程,由语言运行时调度,切换成本极低(纳秒级),可以创建数十万个。
不同语言提供的并发原语:
3.2 异步编程模型:回调 → Promise → async/await 的演进
异步编程允许程序在等待I/O时执行其他任务,避免线程阻塞。理解异步模型是编写高响应、高吞吐应用的基础。
示例15:JavaScript的Promise链与async/await
// 模拟异步操作
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `User${id}` }), 100);
});
}
function fetchPosts(userId) {
return new Promise(resolve => {
setTimeout(() => resolve([`Post1 by ${userId}`, `Post2 by ${userId}`]), 100);
});
}
// Promise链式调用
fetchUser(1)
.then(user => {
console.log(user.name);
return fetchPosts(user.id);
})
.then(posts => {
console.log(posts);
})
.catch(err => console.error(err));
// async/await 更清晰的写法
async function displayUserContent(id) {
try {
const user = await fetchUser(id);
console.log(user.name);
const posts = await fetchPosts(user.id);
console.log(posts);
} catch (err) {
console.error(err);
}
}
displayUserContent(2);
深度解释:async函数返回一个Promise,await会暂停当前函数的执行,直到Promise完成,但不会阻塞事件循环。这比回调地狱(Callback Hell)可读性高得多。
示例16:Python的asyncio实现并发网络请求
import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'http://example.com',
'http://example.org',
'http://example.net',
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, html in zip(urls, results):
print(f"{url}: {len(html)} bytes")
asyncio.run(main())
关键点:asyncio.gather并发执行多个协程。在等待网络响应时,事件循环可以切换到其他协程,实现高效I/O并发。
3.3 多线程同步:锁、原子操作、并发集合
当多个线程共享可变状态时,必须使用同步机制来保证数据一致性。
示例17:Java中的线程安全计数器比较
// 非线程安全
class UnsafeCounter {
private int count = 0;
public void increment() { count++; } // 多线程下丢失更新
public int get() { return count; }
}
// 使用synchronized
class SynchronizedCounter {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized int get() { return count; }
}
// 使用AtomicInteger(无锁CAS)
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
public int get() { return count.get(); }
}
// 使用LongAdder(高并发下更好)
import java.util.concurrent.atomic.LongAdder;
class AdderCounter {
private LongAdder count = new LongAdder();
public void increment() { count.increment(); }
public long get() { return count.sum(); }
}
性能对比:synchronized适合低竞争场景;AtomicInteger基于CAS,没有锁的开销,但在高竞争下可能自旋;LongAdder通过分段计数进一步减少竞争。进阶开发者会根据场景选择合适的同步工具。
示例18:Go的Mutex和RWMutex
type SafeCounter struct {
mu sync.Mutex
m map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.m[key]
}
// 读多写少场景使用RWMutex,允许并发读
type SafeMap struct {
mu sync.RWMutex
m map[string]string
}
func (s *SafeMap) Get(key string) string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.m[key]
}
3.4 数据竞争与死锁的检测与避免
数据竞争:多个线程同时访问同一内存位置,至少有一个是写操作,且没有同步。Go语言内置了数据竞争检测器:go test -race。
死锁:两个或多个线程互相等待对方持有的锁,导致永久阻塞。
示例19:Java死锁示例及解决方法
public class DeadlockDemo {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized(lock1) {
sleep(100); // 模拟工作
synchronized(lock2) {
System.out.println("method1 done");
}
}
}
public void method2() {
synchronized(lock2) {
sleep(100);
synchronized(lock1) {
System.out.println("method2 done");
}
}
}
private void sleep(int ms) { try { Thread.sleep(ms); } catch(Exception e) {} }
public static void main(String[] args) {
DeadlockDemo demo = new DeadlockDemo();
new Thread(demo::method1).start();
new Thread(demo::method2).start();
}
}
避免死锁的原则:
固定锁顺序:总是以相同的顺序获取锁。例如,总是先获取lock1再获取lock2。
使用超时尝试:tryLock(timeout)(Java),如果获取失败则释放已持有的锁并重试。
减少锁粒度:使用并发集合(ConcurrentHashMap)或无锁数据结构。
使用更高级的同步工具:如java.util.concurrent的Semaphore、CountDownLatch等。
来源:
https://zlpow.cn/