C# 并发编程

一、并发编程基础

并发编程是指让程序可以同时执行多个任务,提高资源利用率和程序响应速度。在 C# 中,常见的并发编程技术包括:

  • 线程(Thread)
  • 线程池(ThreadPool)
  • 任务(Task)
  • 异步(async/await)
  • 并行(Parallel 类)
  • 锁和同步机制

二、线程(Thread)

最基础的并发单位,直接操作系统线程。

using System.Threading;

Thread t = new Thread(() =>
{
    Console.WriteLine("线程工作中...");
});
t.Start();

**注意:**直接使用 Thread 灵活但开销大,不推荐频繁创建销毁。


三、线程池(ThreadPool)

.NET 提供了线程池,复用线程,适合短小任务。

ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine("线程池任务");
});

四、任务(Task)

Task 是 .NET 并发编程的主流方式,支持任务调度、取消、异常处理等。

using System.Threading.Tasks;

Task.Run(() =>
{
    Console.WriteLine("通过 Task 执行任务");
});

等待任务完成

Task t = Task.Run(() => { /* ... */ });
t.Wait(); // 阻塞等待

获取返回值

Task<int> t = Task.Run(() => 42);
int result = t.Result;

五、异步编程(async/await)

让异步代码像同步一样易于编写和理解。

public async Task<int> GetDataAsync()
{
    await Task.Delay(1000); // 模拟异步操作
    return 123;
}

int data = await GetDataAsync();

优点:不会阻塞主线程,适合 IO 密集型场景。


六、并行编程(Parallel 类)

用于数据或任务的并行处理。

并行循环

using System.Threading.Tasks;

Parallel.For(0, 10, i =>
{
    Console.WriteLine($"并行处理:{i}");
});

并行集合处理

string[] arr = { "A", "B", "C" };
Parallel.ForEach(arr, item =>
{
    Console.WriteLine(item);
});

七、同步与锁

多线程访问共享资源时需要同步,避免数据竞争。

lock 关键字

private object _lockObj = new object();

void SafeMethod()
{
    lock (_lockObj)
    {
        // 线程安全代码块
    }
}

其他同步机制

  • Monitor
  • Mutex
  • Semaphore
  • ReaderWriterLockSlim

八、线程安全集合

.NET 提供了线程安全的集合类,如:

  • ConcurrentDictionary<TKey, TValue>
  • ConcurrentQueue<T>
  • ConcurrentBag<T>
  • BlockingCollection<T>
var dict = new ConcurrentDictionary<int, string>();
dict.TryAdd(1, "A");

九、取消任务(CancellationToken)

可以优雅地取消正在执行的任务。

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    while (!cts.Token.IsCancellationRequested)
    {
        // 执行任务
    }
}, cts.Token);

cts.Cancel(); // 请求取消

十、死锁与竞态条件

死锁:多个线程互相等待对方释放资源,导致程序卡死。
竞态条件:多个线程同时修改数据,导致结果不可预测。

避免方法:

  • 只锁必要代码
  • 保持锁顺序一致
  • 使用高层并发原语(如线程安全集合)

十一、异步与并发的区别

  • 异步:强调非阻塞,通常用于 IO 操作。
  • 并发:强调同时运行,通常用于多核计算。

十二、并发编程最佳实践

  • 优先使用高层 API(如 Task、Parallel、线程安全集合)
  • 保持代码简单,避免复杂锁逻辑
  • 只在必要时用低层 API(Thread、Monitor)
  • 注意异常处理和任务取消
  • 合理拆分任务,避免过度并发

十三、实际应用场景举例

  • Web 服务器异步处理请求
  • 批量数据并行处理
  • 多线程爬虫/下载器
  • 多任务图像处理
  • 实时监控/数据采集

十四、综合示例

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // 并行计算
        Parallel.For(0, 5, i =>
        {
            Console.WriteLine($"并行任务 {i} 由线程 {Thread.CurrentThread.ManagedThreadId} 执行");
        });

        // 异步任务
        int result = await GetNumberAsync();
        Console.WriteLine($"异步结果:{result}");

        // 线程安全集合
        var bag = new System.Collections.Concurrent.ConcurrentBag<int>();
        Parallel.For(0, 100, bag.Add);
        Console.WriteLine($"集合元素数量:{bag.Count}");
    }

    static async Task<int> GetNumberAsync()
    {
        await Task.Delay(500);
        return 42;
    }
}

15. 并发编程中的常见问题与解决方法

1. 死锁(Deadlock)

死锁发生在两个或多个线程相互等待对方释放资源,永远无法继续执行。

避免方法:

  • 保证所有线程获取锁的顺序一致。
  • 尽量减少锁的粒度,锁定最小必要范围。
  • 使用超时锁 Monitor.TryEnter 或 Mutex.WaitOne(timeout)

示例:

object lockA = new object();
object lockB = new object();

void Thread1()
{
    lock(lockA)
    {
        Thread.Sleep(100); // 模拟业务处理
        lock(lockB)
        {
            // ...
        }
    }
}

void Thread2()
{
    lock(lockB)
    {
        Thread.Sleep(100);
        lock(lockA)
        {
            // ...
        }
    }
}

上例可能导致死锁。


2. 竞态条件(Race Condition)

多个线程并发访问和修改同一资源,导致结果不可预测。

解决方法:

  • 使用 lock 或线程安全集合。
  • 尽量减少共享资源。
  • 原子操作(如 Interlocked 类)。

示例:

int count = 0;

void Increase()
{
    Interlocked.Increment(ref count);
}

16. 线程同步机制补充

1. Monitor

比 lock 更灵活,可以实现等待/通知机制。

Monitor.Enter(lockObj);
try
{
    // ...
}
finally
{
    Monitor.Exit(lockObj);
}

2. Mutex

用于跨进程同步,开销较大。

Mutex mutex = new Mutex();
mutex.WaitOne();
try
{
    // ...
}
finally
{
    mutex.ReleaseMutex();
}

3. Semaphore

限制同时访问资源的线程数量。

Semaphore sem = new Semaphore(2, 2); // 最多2个线程同时进入
sem.WaitOne();
try
{
    // ...
}
finally
{
    sem.Release();
}

4. ReaderWriterLockSlim

适合读多写少场景,允许多个线程并发读取,但写入时独占。

ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();

void Read()
{
    rwLock.EnterReadLock();
    try { /* 读操作 */ }
    finally { rwLock.ExitReadLock(); }
}

void Write()
{
    rwLock.EnterWriteLock();
    try { /* 写操作 */ }
    finally { rwLock.ExitWriteLock(); }
}

17. 并发与异步的结合

异步方法本身不一定并发,但可以结合并发技术提升性能。

示例:批量异步处理

async Task ProcessBatchAsync(IEnumerable<string> urls)
{
    var tasks = urls.Select(url => DownloadAsync(url));
    await Task.WhenAll(tasks);
}

async Task<string> DownloadAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

18. 并发限流(限制并发量)

通过 SemaphoreSlim 控制最大并发数,防止资源耗尽。

SemaphoreSlim semaphore = new SemaphoreSlim(5); // 最多5个并发

async Task DownloadAsync(string url)
{
    await semaphore.WaitAsync();
    try
    {
        // 下载逻辑
    }
    finally
    {
        semaphore.Release();
    }
}

19. 线程安全单例模式

单例模式在并发场景下要保证线程安全。

双重检查锁定:

class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                        _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
}

20. 并发编程的调试与分析

  • 使用 Visual Studio 的“并行堆栈”、“线程窗口”调试线程状态。
  • 利用性能分析工具(如 PerfView、dotTrace)分析并发瓶颈。
  • 使用日志和事件追踪定位并发问题。

21. 生产环境中的并发建议

  • 优先用高层 API(Task、Parallel、线程安全集合)。
  • 控制并发量,防止资源耗尽。
  • 关注异常处理和任务取消,避免僵尸线程或死锁。
  • 定期回顾并发代码,防止隐患。

22. 综合并发示例:并发下载器

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Downloader
{
    private SemaphoreSlim _semaphore = new SemaphoreSlim(3); // 最多3个并发

    public async Task DownloadAllAsync(IEnumerable<string> urls)
    {
        var tasks = new List<Task>();
        foreach (var url in urls)
        {
            tasks.Add(DownloadAsync(url));
        }
        await Task.WhenAll(tasks);
    }

    private async Task DownloadAsync(string url)
    {
        await _semaphore.WaitAsync();
        try
        {
            using var client = new HttpClient();
            var content = await client.GetStringAsync(url);
            Console.WriteLine($"下载完成: {url}, 长度: {content.Length}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"下载失败: {url}, 错误: {ex.Message}");
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

23. 并发数据结构与集合

.NET 提供了一系列线程安全的数据结构,适合高并发场景:

  • ConcurrentQueue:线程安全队列,适合生产者-消费者模型。
  • ConcurrentStack:线程安全栈。
  • ConcurrentBag:适合多线程收集数据。
  • ConcurrentDictionary<TKey, TValue>:线程安全字典,支持高效并发读写。
  • BlockingCollection:包装了线程安全集合,支持阻塞和限流。

示例:生产者-消费者模型

using System.Collections.Concurrent;
using System.Threading.Tasks;

ConcurrentQueue<int> queue = new ConcurrentQueue<int>();

// 生产者
Task.Run(() => {
    for (int i = 0; i < 100; i++)
    {
        queue.Enqueue(i);
    }
});

// 消费者
Task.Run(() => {
    int item;
    while (true)
    {
        if (queue.TryDequeue(out item))
        {
            Console.WriteLine($"消费:{item}");
        }
    }
});

24. 并发中的异常处理

并发任务出现异常时,要统一处理,避免异常丢失。

Task 异常处理

var task = Task.Run(() => throw new Exception("出错了!"));
try
{
    task.Wait();
}
catch (AggregateException ex)
{
    foreach (var inner in ex.InnerExceptions)
        Console.WriteLine(inner.Message);
}

异步方法异常处理

try
{
    await SomeAsyncMethod();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

25. 并发中的任务取消与超时

利用 CancellationTokenSource 实现任务取消和超时控制。

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); // 3秒超时

try
{
    await Task.Run(() =>
    {
        while (!cts.Token.IsCancellationRequested)
        {
            // 模拟长时间任务
        }
    }, cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("任务已取消或超时");
}

26. 线程局部存储(ThreadLocal)

有些数据只需在当前线程内维护,使用 ThreadLocal<T>

ThreadLocal<int> threadId = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);

Task.Run(() => Console.WriteLine(threadId.Value));
Task.Run(() => Console.WriteLine(threadId.Value));

27. 并发编程中的内存可见性(volatile)

volatile 关键字保证变量的修改对所有线程立即可见,适合简单标志。

volatile bool _running = true;

void Worker()
{
    while (_running)
    {
        // ...
    }
}

28. 并发与异步的性能对比与选择

  • CPU密集型任务:推荐用 Parallel.ForTask.Run,充分利用多核。
  • IO密集型任务:推荐用 async/await,减少线程阻塞。
  • 高并发数据处理:用线程安全集合,减少锁冲突。

29. 并发编程的常见设计模式

  • 生产者-消费者:BlockingCollection、ConcurrentQueue
  • 线程池模式:Task、ThreadPool
  • 异步回调模式:Task.ContinueWith、事件
  • 限流模式:SemaphoreSlim

30. 并发编程的测试与调试

  • 使用 Thread.SleepManualResetEvent 等模拟并发环境。
  • 利用 Visual Studio 的并行调试工具分析线程状态。
  • 利用 ConcurrentQueue、日志等记录并发行为,便于问题定位。

31. 并发编程的常见面试题

  1. 如何实现线程安全的单例?
  2. 说说 lock 和 Monitor 的区别。
  3. 如何避免死锁?
  4. 如何让多个任务并发执行,并处理所有结果?
  5. 并发队列和普通队列的区别?
  6. async/await 的底层原理?

32. 真实项目中的并发优化建议

  • 业务数据分片,减少共享资源。
  • 用线程安全集合替代锁。
  • 控制并发量,防止资源耗尽(如数据库连接池、限流)。
  • 合理拆分任务,避免“线程爆炸”。
  • 关注内存泄漏和资源释放。
  • 定期回顾并发代码,利用代码审查发现隐患。

33. 综合并发实战案例:高并发爬虫

using System;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Threading.Tasks;

class Spider
{
    private SemaphoreSlim _semaphore = new SemaphoreSlim(10); // 最大并发10
    private ConcurrentBag<string> _results = new ConcurrentBag<string>();

    public async Task CrawlAsync(string[] urls)
    {
        var tasks = new Task[urls.Length];
        for (int i = 0; i < urls.Length; i++)
        {
            tasks[i] = CrawlOneAsync(urls[i]);
        }
        await Task.WhenAll(tasks);
        Console.WriteLine($"抓取完成,结果数:{_results.Count}");
    }

    private async Task CrawlOneAsync(string url)
    {
        await _semaphore.WaitAsync();
        try
        {
            using var client = new HttpClient();
            var html = await client.GetStringAsync(url);
            _results.Add(html);
            Console.WriteLine($"抓取:{url},长度:{html.Length}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"抓取失败:{url},错误:{ex.Message}");
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值