|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. 引言
在现代软件开发中,多线程编程已成为提升应用程序性能和用户体验的关键技术。对于C#开发者而言,深入理解线程与进程的概念、区别以及在.NET框架下的并发处理机制,是构建高效、响应迅速的应用程序的基础。本文将详细探讨这些核心概念,并通过实际代码示例展示如何在C#中应用多线程编程技术。
2. 进程与线程的基本概念
2.1 进程
进程是操作系统进行资源分配和调度的基本单位,是应用程序的执行实例。每个进程都有独立的内存空间、系统资源和执行状态。在Windows系统中,当我们打开一个应用程序(如Word、浏览器等),操作系统就会创建一个进程来运行该应用程序。
进程的主要特点包括:
• 独立性:每个进程有独立的地址空间,一个进程的崩溃不会直接影响其他进程。
• 资源拥有:进程是资源拥有的基本单位,拥有独立的内存、文件句柄等资源。
• 上下文切换开销大:由于进程间的独立性,进程间的切换需要保存和恢复更多的上下文信息,因此开销较大。
在C#中,我们可以通过Process类来与系统进程进行交互:
- using System.Diagnostics;
- class ProcessExample
- {
- static void Main()
- {
- // 获取当前进程
- Process currentProcess = Process.GetCurrentProcess();
- Console.WriteLine($"当前进程ID: {currentProcess.Id}");
- Console.WriteLine($"进程名称: {currentProcess.ProcessName}");
- Console.WriteLine($"启动时间: {currentProcess.StartTime}");
-
- // 启动一个新的进程
- try
- {
- Process notepadProcess = new Process();
- notepadProcess.StartInfo.FileName = "notepad.exe";
- notepadProcess.StartInfo.Arguments = "";
- notepadProcess.Start();
-
- Console.WriteLine($"已启动记事本进程,ID: {notepadProcess.Id}");
-
- // 等待用户输入后关闭进程
- Console.WriteLine("按任意键关闭记事本进程...");
- Console.ReadKey();
-
- if (!notepadProcess.HasExited)
- {
- notepadProcess.CloseMainWindow();
- notepadProcess.Close();
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"启动进程时出错: {ex.Message}");
- }
- }
- }
复制代码
2.2 线程
线程是进程中的一个执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件句柄等,但每个线程有独立的执行栈、程序计数器和局部变量。
线程的主要特点包括:
• 轻量级:线程的创建和切换开销远小于进程。
• 共享资源:同一进程内的线程共享进程的资源,使得线程间通信更加方便。
• 并发性:多个线程可以并发执行,提高应用程序的响应性和吞吐量。
在C#中,我们可以通过Thread类来创建和管理线程:
- using System;
- using System.Threading;
- class ThreadExample
- {
- static void Main()
- {
- // 获取主线程信息
- Thread mainThread = Thread.CurrentThread;
- Console.WriteLine($"主线程ID: {mainThread.ManagedThreadId}");
-
- // 创建并启动一个新线程
- Thread workerThread = new Thread(WorkerMethod);
- workerThread.Name = "WorkerThread";
- workerThread.Start();
-
- // 主线程继续执行
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine($"主线程执行: {i}");
- Thread.Sleep(500);
- }
-
- // 等待工作线程完成
- workerThread.Join();
- Console.WriteLine("工作线程已完成,主线程退出。");
- }
-
- static void WorkerMethod()
- {
- Thread currentThread = Thread.CurrentThread;
- Console.WriteLine($"工作线程 {currentThread.Name} 开始执行,ID: {currentThread.ManagedThreadId}");
-
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine($"工作线程执行: {i}");
- Thread.Sleep(1000);
- }
-
- Console.WriteLine($"工作线程 {currentThread.Name} 执行完成");
- }
- }
复制代码
3. 线程与进程的区别
理解线程与进程的区别对于多线程编程至关重要。以下是它们之间的主要区别:
3.1 资源分配
• 进程:操作系统分配资源的基本单位,每个进程有独立的地址空间、代码、数据和资源。
• 线程:CPU调度的基本单位,同一进程内的线程共享进程的资源,包括内存空间、文件句柄等。
3.2 上下文切换
• 进程:上下文切换开销大,需要保存和恢复整个进程的地址空间、寄存器状态等。
• 线程:上下文切换开销小,只需要保存和恢复线程的寄存器状态和栈信息。
3.3 通信方式
• 进程:进程间通信(IPC)需要使用特定的机制,如管道、消息队列、共享内存等,相对复杂。
• 线程:线程间通信可以直接通过共享内存进行,更加简单高效。
3.4 健壮性
• 进程:一个进程的崩溃不会影响其他进程,系统更加稳定。
• 线程:一个线程的崩溃可能导致整个进程的崩溃,稳定性相对较低。
3.5 创建和销毁
• 进程:创建和销毁的开销大,速度慢。
• 线程:创建和销毁的开销小,速度快。
下面通过一个示例来展示进程和线程在资源使用上的区别:
- using System;
- using System.Diagnostics;
- using System.Threading;
- class ProcessVsThreadExample
- {
- static void Main()
- {
- Console.WriteLine("比较进程和线程的创建开销");
-
- // 测试创建多个进程的开销
- Stopwatch processStopwatch = Stopwatch.StartNew();
- for (int i = 0; i < 5; i++)
- {
- Process process = new Process();
- process.StartInfo.FileName = "notepad.exe";
- process.Start();
- process.CloseMainWindow();
- process.WaitForExit();
- }
- processStopwatch.Stop();
-
- Console.WriteLine($"创建5个进程耗时: {processStopwatch.ElapsedMilliseconds} 毫秒");
-
- // 测试创建多个线程的开销
- Stopwatch threadStopwatch = Stopwatch.StartNew();
- Thread[] threads = new Thread[5];
- for (int i = 0; i < 5; i++)
- {
- threads[i] = new Thread(WorkerMethod);
- threads[i].Start();
- }
-
- // 等待所有线程完成
- foreach (Thread thread in threads)
- {
- thread.Join();
- }
- threadStopwatch.Stop();
-
- Console.WriteLine($"创建5个线程耗时: {threadStopwatch.ElapsedMilliseconds} 毫秒");
- }
-
- static void WorkerMethod()
- {
- // 模拟一些工作
- Thread.Sleep(100);
- }
- }
复制代码
4. C#中的线程处理
4.1 Thread类
System.Threading.Thread类是C#中最基本的线程操作类,它提供了创建、控制和管理线程的方法。
- using System;
- using System.Threading;
- class ThreadClassExample
- {
- static void Main()
- {
- // 创建线程但不立即启动
- Thread thread = new Thread(new ThreadStart(ExecuteTask));
-
- // 设置线程属性
- thread.Name = "WorkerThread";
- thread.IsBackground = false; // 前台线程
-
- // 启动线程
- thread.Start();
-
- // 检查线程状态
- Console.WriteLine($"线程状态: {thread.ThreadState}");
-
- // 等待线程完成
- thread.Join();
- Console.WriteLine("线程执行完成");
- }
-
- static void ExecuteTask()
- {
- Console.WriteLine($"{Thread.CurrentThread.Name} 开始执行");
-
- // 模拟耗时操作
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine($"{Thread.CurrentThread.Name} 执行中: {i}");
- Thread.Sleep(500);
- }
-
- Console.WriteLine($"{Thread.CurrentThread.Name} 执行完成");
- }
- }
复制代码
4.2 线程池
线程池是一种基于池化思想管理线程的技术,它可以避免频繁创建和销毁线程所带来的开销。在.NET中,ThreadPool类提供了对线程池的访问。
- using System;
- using System.Threading;
- class ThreadPoolExample
- {
- static void Main()
- {
- // 获取线程池信息
- int workerThreads;
- int completionPortThreads;
- ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
- Console.WriteLine($"最大工作线程数: {workerThreads}");
- Console.WriteLine($"最大I/O线程数: {completionPortThreads}");
-
- // 使用线程池执行任务
- for (int i = 0; i < 5; i++)
- {
- int taskNum = i;
- ThreadPool.QueueUserWorkItem(state =>
- {
- Console.WriteLine($"任务 {taskNum} 在线程池线程上执行,线程ID: {Thread.CurrentThread.ManagedThreadId}");
- Thread.Sleep(1000);
- Console.WriteLine($"任务 {taskNum} 完成");
- });
- }
-
- // 等待所有任务完成
- Console.WriteLine("按任意键退出...");
- Console.ReadKey();
- }
- }
复制代码
4.3 Task类
.NET Framework 4引入了System.Threading.Tasks命名空间,提供了更高级的抽象来表示异步操作。Task类是任务并行库(TPL)的核心,它提供了比直接使用Thread或ThreadPool更简单、更强大的API。
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- class TaskExample
- {
- static void Main()
- {
- // 创建并启动一个Task
- Task task1 = Task.Run(() =>
- {
- Console.WriteLine($"Task 1 在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
- Thread.Sleep(1000);
- Console.WriteLine("Task 1 完成");
- });
-
- // 创建并启动一个带返回值的Task
- Task<int> task2 = Task.Run(() =>
- {
- Console.WriteLine($"Task 2 在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
- Thread.Sleep(1500);
- return 42;
- });
-
- // 使用ContinueWith在任务完成后继续执行
- task1.ContinueWith(t =>
- {
- Console.WriteLine("Task 1 的后续操作");
- });
-
- // 等待所有任务完成
- Task.WaitAll(task1, task2);
-
- // 获取Task的结果
- Console.WriteLine($"Task 2 的结果: {task2.Result}");
-
- // 使用Task.Factory创建任务
- Task task3 = Task.Factory.StartNew(() =>
- {
- Console.WriteLine($"Task 3 在线程 {Thread.CurrentThread.ManagedThreadId} 上执行");
- Thread.Sleep(500);
- Console.WriteLine("Task 3 完成");
- });
-
- task3.Wait();
- Console.WriteLine("所有任务完成");
- }
- }
复制代码
4.4 async/await模式
C# 5引入了async和await关键字,使得异步编程更加直观和简洁。这种模式特别适合I/O密集型操作。
- using System;
- using System.Net.Http;
- using System.Threading.Tasks;
- class AsyncAwaitExample
- {
- static async Task Main(string[] args)
- {
- Console.WriteLine("开始执行异步操作");
-
- // 调用异步方法
- string result = await DownloadContentAsync("https://www.example.com");
- Console.WriteLine($"下载的内容长度: {result.Length}");
-
- Console.WriteLine("按任意键退出...");
- Console.ReadKey();
- }
-
- static async Task<string> DownloadContentAsync(string url)
- {
- using (HttpClient client = new HttpClient())
- {
- Console.WriteLine("开始下载内容...");
-
- // 异步下载内容
- string content = await client.GetStringAsync(url);
-
- Console.WriteLine("内容下载完成");
- return content;
- }
- }
- }
复制代码
5. .NET框架下的并发处理机制
5.1 同步原语
在多线程编程中,同步原语用于控制多个线程对共享资源的访问,防止数据竞争和不一致。
lock语句是C#中最简单的同步机制,用于确保代码块在同一时间只能被一个线程执行。
- using System;
- using System.Threading;
- class LockExample
- {
- private static readonly object _lockObject = new object();
- private static int _counter = 0;
-
- static void Main()
- {
- // 创建多个线程来增加计数器
- Thread[] threads = new Thread[10];
- for (int i = 0; i < threads.Length; i++)
- {
- threads[i] = new Thread(IncrementCounter);
- threads[i].Start();
- }
-
- // 等待所有线程完成
- foreach (Thread thread in threads)
- {
- thread.Join();
- }
-
- Console.WriteLine($"最终计数器值: {_counter}");
- }
-
- static void IncrementCounter()
- {
- for (int i = 0; i < 1000; i++)
- {
- // 使用lock确保对_counter的原子操作
- lock (_lockObject)
- {
- _counter++;
- }
- }
- }
- }
复制代码
Monitor类提供了更细粒度的控制,包括尝试获取锁而不阻塞、指定超时等。
- using System;
- using System.Threading;
- class MonitorExample
- {
- private static readonly object _lockObject = new object();
- private static int _sharedResource = 0;
-
- static void Main()
- {
- Thread thread1 = new Thread(() => AccessResource("Thread1"));
- Thread thread2 = new Thread(() => AccessResource("Thread2"));
-
- thread1.Start();
- thread2.Start();
-
- thread1.Join();
- thread2.Join();
-
- Console.WriteLine($"最终资源值: {_sharedResource}");
- }
-
- static void AccessResource(string threadName)
- {
- Console.WriteLine($"{threadName} 尝试获取锁...");
-
- // 尝试获取锁,最多等待1秒
- if (Monitor.TryEnter(_lockObject, 1000))
- {
- try
- {
- Console.WriteLine($"{threadName} 获取了锁,正在访问资源...");
-
- // 模拟工作
- for (int i = 0; i < 5; i++)
- {
- _sharedResource++;
- Console.WriteLine($"{threadName} 更新资源为: {_sharedResource}");
- Thread.Sleep(500);
- }
- }
- finally
- {
- // 确保锁被释放
- Monitor.Exit(_lockObject);
- Console.WriteLine($"{threadName} 释放了锁");
- }
- }
- else
- {
- Console.WriteLine($"{threadName} 无法获取锁,超时");
- }
- }
- }
复制代码
Mutex(互斥体)是一种同步原语,可以用于进程间同步。
- using System;
- using System.Threading;
- class MutexExample
- {
- // 创建一个命名的Mutex,可用于跨进程同步
- private static Mutex _mutex = new Mutex(false, "MyNamedMutex");
-
- static void Main()
- {
- Console.WriteLine("应用程序启动,尝试获取互斥体...");
-
- // 尝试获取互斥体
- if (_mutex.WaitOne(3000))
- {
- try
- {
- Console.WriteLine("获取互斥体成功,执行关键操作...");
-
- // 模拟关键操作
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine($"执行操作: {i + 1}/5");
- Thread.Sleep(1000);
- }
- }
- finally
- {
- // 释放互斥体
- _mutex.ReleaseMutex();
- Console.WriteLine("互斥体已释放");
- }
- }
- else
- {
- Console.WriteLine("获取互斥体失败,可能另一个实例正在运行");
- }
-
- Console.WriteLine("按任意键退出...");
- Console.ReadKey();
- }
- }
复制代码
Semaphore(信号量)用于限制同时访问某一资源的线程数量。
- using System;
- using System.Threading;
- class SemaphoreExample
- {
- // 创建一个信号量,最多允许3个线程同时访问
- private static Semaphore _semaphore = new Semaphore(3, 3);
-
- static void Main()
- {
- // 创建并启动10个工作线程
- for (int i = 0; i < 10; i++)
- {
- int threadNum = i;
- Thread workerThread = new Thread(() => AccessResource(threadNum));
- workerThread.Name = $"Thread {i}";
- workerThread.Start();
- }
-
- Console.WriteLine("按任意键退出...");
- Console.ReadKey();
- }
-
- static void AccessResource(int threadNum)
- {
- Console.WriteLine($"线程 {threadNum} 等待访问资源...");
-
- // 等待信号量
- _semaphore.WaitOne();
-
- try
- {
- Console.WriteLine($"线程 {threadNum} 获得访问权限,正在使用资源...");
-
- // 模拟使用资源
- Thread.Sleep(2000);
-
- Console.WriteLine($"线程 {threadNum} 完成资源使用");
- }
- finally
- {
- // 释放信号量
- _semaphore.Release();
- }
- }
- }
复制代码
5.2 线程安全集合
.NET Framework提供了一些线程安全的集合类,用于在多线程环境中安全地操作数据。
ConcurrentDictionary是线程安全的字典集合。
- using System;
- using System.Collections.Concurrent;
- using System.Threading;
- using System.Threading.Tasks;
- class ConcurrentDictionaryExample
- {
- static void Main()
- {
- // 创建线程安全的字典
- ConcurrentDictionary<string, int> dictionary = new ConcurrentDictionary<string, int>();
-
- // 创建多个任务并发操作字典
- Task[] tasks = new Task[10];
- for (int i = 0; i < tasks.Length; i++)
- {
- int taskNum = i;
- tasks[i] = Task.Run(() =>
- {
- for (int j = 0; j < 100; j++)
- {
- string key = $"Key-{taskNum}-{j}";
-
- // 使用AddOrUpdate确保线程安全
- dictionary.AddOrUpdate(key, 1, (k, v) => v + 1);
-
- // 模拟一些工作
- Thread.Sleep(10);
- }
- });
- }
-
- // 等待所有任务完成
- Task.WaitAll(tasks);
-
- // 输出结果
- Console.WriteLine($"字典中的项数: {dictionary.Count}");
-
- // 检查一些特定键的值
- for (int i = 0; i < 3; i++)
- {
- string key = $"Key-{i}-0";
- if (dictionary.TryGetValue(key, out int value))
- {
- Console.WriteLine($"{key} 的值: {value}");
- }
- }
- }
- }
复制代码
ConcurrentQueue是线程安全的先进先出(FIFO)集合。
- using System;
- using System.Collections.Concurrent;
- using System.Threading;
- using System.Threading.Tasks;
- class ConcurrentQueueExample
- {
- static void Main()
- {
- // 创建线程安全的队列
- ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
-
- // 创建生产者任务
- Task producer = Task.Run(() =>
- {
- for (int i = 0; i < 100; i++)
- {
- queue.Enqueue(i);
- Console.WriteLine($"生产: {i}");
- Thread.Sleep(50);
- }
- });
-
- // 创建消费者任务
- Task consumer = Task.Run(() =>
- {
- int value;
- while (true)
- {
- if (queue.TryDequeue(out value))
- {
- Console.WriteLine($"消费: {value}");
- Thread.Sleep(100);
- }
- else if (producer.IsCompleted)
- {
- break;
- }
- }
- });
-
- // 等待所有任务完成
- Task.WaitAll(producer, consumer);
-
- Console.WriteLine($"队列中的剩余项数: {queue.Count}");
- }
- }
复制代码
5.3 并行编程
.NET Framework提供了并行编程支持,可以充分利用多核处理器的计算能力。
Parallel类提供了数据并行和任务并行的简化方法。
- using System;
- using System.Threading.Tasks;
- class ParallelExample
- {
- static void Main()
- {
- // 使用Parallel.For进行数据并行
- Console.WriteLine("Parallel.For 示例:");
- Parallel.For(0, 10, i =>
- {
- Console.WriteLine($"迭代 {i} 在线程 {Task.CurrentId} 上执行");
- Task.Delay(100).Wait(); // 模拟工作
- });
-
- Console.WriteLine();
-
- // 使用Parallel.ForEach进行数据并行
- Console.WriteLine("Parallel.ForEach 示例:");
- string[] items = { "A", "B", "C", "D", "E" };
- Parallel.ForEach(items, item =>
- {
- Console.WriteLine($"处理 {item} 在线程 {Task.CurrentId} 上执行");
- Task.Delay(100).Wait(); // 模拟工作
- });
-
- Console.WriteLine();
-
- // 使用Parallel.Invoke进行任务并行
- Console.WriteLine("Parallel.Invoke 示例:");
- Parallel.Invoke(
- () => {
- Console.WriteLine($"任务1 在线程 {Task.CurrentId} 上执行");
- Task.Delay(500).Wait();
- },
- () => {
- Console.WriteLine($"任务2 在线程 {Task.CurrentId} 上执行");
- Task.Delay(300).Wait();
- },
- () => {
- Console.WriteLine($"任务3 在线程 {Task.CurrentId} 上执行");
- Task.Delay(400).Wait();
- }
- );
-
- Console.WriteLine("所有并行操作完成");
- }
- }
复制代码
PLINQ是LINQ的并行实现,可以自动并行化查询操作。
- using System;
- using System.Linq;
- class PLINQExample
- {
- static void Main()
- {
- // 创建一个大数组
- int[] numbers = Enumerable.Range(1, 10000000).ToArray();
-
- Console.WriteLine("开始处理大数据集...");
-
- // 使用普通LINQ查询
- var linqStartTime = DateTime.Now;
- var linqResult = numbers.Where(n => n % 2 == 0)
- .Select(n => n * 2)
- .Take(100)
- .ToList();
- var linqEndTime = DateTime.Now;
-
- Console.WriteLine($"LINQ处理时间: {(linqEndTime - linqStartTime).TotalMilliseconds} 毫秒");
-
- // 使用PLINQ并行查询
- var plinqStartTime = DateTime.Now;
- var plinqResult = numbers.AsParallel()
- .Where(n => n % 2 == 0)
- .Select(n => n * 2)
- .Take(100)
- .ToList();
- var plinqEndTime = DateTime.Now;
-
- Console.WriteLine($"PLINQ处理时间: {(plinqEndTime - plinqStartTime).TotalMilliseconds} 毫秒");
-
- // 验证结果是否相同
- Console.WriteLine($"结果相同: {linqResult.SequenceEqual(plinqResult)}");
-
- // 使用WithDegreeOfParallelism控制并行度
- var controlledStartTime = DateTime.Now;
- var controlledResult = numbers.AsParallel()
- .WithDegreeOfParallelism(4) // 限制最多使用4个处理器
- .Where(n => n % 2 == 0)
- .Select(n => n * 2)
- .Take(100)
- .ToList();
- var controlledEndTime = DateTime.Now;
-
- Console.WriteLine($"控制并行度的PLINQ处理时间: {(controlledEndTime - controlledStartTime).TotalMilliseconds} 毫秒");
-
- // 使用AsOrdered保持原始顺序
- var orderedStartTime = DateTime.Now;
- var orderedResult = numbers.AsParallel()
- .AsOrdered() // 保持原始顺序
- .Where(n => n % 2 == 0)
- .Select(n => n * 2)
- .Take(100)
- .ToList();
- var orderedEndTime = DateTime.Now;
-
- Console.WriteLine($"保持顺序的PLINQ处理时间: {(orderedEndTime - orderedStartTime).TotalMilliseconds} 毫秒");
- }
- }
复制代码
6. 多线程编程的应用场景
6.1 GUI应用程序
在GUI应用程序中,使用多线程可以避免界面冻结,提高用户体验。
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- namespace GUIExample
- {
- public partial class MainForm : Form
- {
- public MainForm()
- {
- InitializeComponent();
- }
-
- private void btnProcess_Click(object sender, EventArgs e)
- {
- // 禁用按钮,防止重复点击
- btnProcess.Enabled = false;
-
- // 显示进度条
- progressBar.Visible = true;
- progressBar.Value = 0;
-
- // 使用Task.Run在后台线程执行耗时操作
- Task.Run(() =>
- {
- for (int i = 0; i <= 100; i++)
- {
- // 模拟耗时操作
- Thread.Sleep(100);
-
- // 更新UI(必须使用Control.Invoke或Control.BeginInvoke)
- this.Invoke((MethodInvoker)delegate
- {
- progressBar.Value = i;
- lblStatus.Text = $"处理进度: {i}%";
- });
- }
-
- // 操作完成后更新UI
- this.Invoke((MethodInvoker)delegate
- {
- lblStatus.Text = "处理完成!";
- btnProcess.Enabled = true;
- });
- });
- }
- }
- }
复制代码
6.2 服务器应用程序
在服务器应用程序中,多线程可以同时处理多个客户端请求,提高服务器的吞吐量。
- using System;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- class ServerExample
- {
- private static bool _isRunning = false;
- private static TcpListener _listener;
-
- static void Main()
- {
- // 启动服务器
- StartServer(8888);
-
- Console.WriteLine("服务器已启动,按Q键停止服务器...");
-
- // 等待用户输入以停止服务器
- while (Console.ReadKey().Key != ConsoleKey.Q) { }
-
- // 停止服务器
- StopServer();
- }
-
- static void StartServer(int port)
- {
- _listener = new TcpListener(IPAddress.Any, port);
- _listener.Start();
- _isRunning = true;
-
- // 创建一个线程来监听客户端连接
- Thread listenerThread = new Thread(() =>
- {
- while (_isRunning)
- {
- try
- {
- // 接受客户端连接
- TcpClient client = _listener.AcceptTcpClient();
- Console.WriteLine("客户端已连接");
-
- // 为每个客户端创建一个处理线程
- Thread clientThread = new Thread(() => HandleClient(client));
- clientThread.Start();
- }
- catch (Exception ex)
- {
- Console.WriteLine($"监听错误: {ex.Message}");
- }
- }
- });
-
- listenerThread.Start();
- }
-
- static void StopServer()
- {
- _isRunning = false;
- _listener?.Stop();
- Console.WriteLine("服务器已停止");
- }
-
- static void HandleClient(TcpClient client)
- {
- try
- {
- using (NetworkStream stream = client.GetStream())
- {
- byte[] buffer = new byte[1024];
- int bytesRead;
-
- // 读取客户端发送的数据
- bytesRead = stream.Read(buffer, 0, buffer.Length);
- string clientMessage = Encoding.ASCII.GetString(buffer, 0, bytesRead);
- Console.WriteLine($"收到消息: {clientMessage}");
-
- // 处理请求(模拟耗时操作)
- Thread.Sleep(1000);
-
- // 发送响应
- string response = $"服务器响应: {clientMessage.ToUpper()}";
- byte[] responseBytes = Encoding.ASCII.GetBytes(response);
- stream.Write(responseBytes, 0, responseBytes.Length);
- Console.WriteLine($"已发送响应: {response}");
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"处理客户端时出错: {ex.Message}");
- }
- finally
- {
- client.Close();
- Console.WriteLine("客户端连接已关闭");
- }
- }
- }
复制代码
6.3 并行计算
对于计算密集型任务,使用多线程可以充分利用多核处理器的计算能力。
- using System;
- using System.Diagnostics;
- using System.Threading.Tasks;
- class ParallelComputingExample
- {
- static void Main()
- {
- // 创建大型矩阵
- int size = 1000;
- double[,] matrixA = GenerateRandomMatrix(size, size);
- double[,] matrixB = GenerateRandomMatrix(size, size);
-
- // 串行矩阵乘法
- var serialStopwatch = Stopwatch.StartNew();
- double[,] serialResult = SerialMatrixMultiply(matrixA, matrixB);
- serialStopwatch.Stop();
- Console.WriteLine($"串行矩阵乘法耗时: {serialStopwatch.ElapsedMilliseconds} 毫秒");
-
- // 并行矩阵乘法
- var parallelStopwatch = Stopwatch.StartNew();
- double[,] parallelResult = ParallelMatrixMultiply(matrixA, matrixB);
- parallelStopwatch.Stop();
- Console.WriteLine($"并行矩阵乘法耗时: {parallelStopwatch.ElapsedMilliseconds} 毫秒");
-
- // 验证结果是否相同
- bool resultsEqual = true;
- for (int i = 0; i < size; i++)
- {
- for (int j = 0; j < size; j++)
- {
- if (Math.Abs(serialResult[i, j] - parallelResult[i, j]) > 0.000001)
- {
- resultsEqual = false;
- break;
- }
- }
- if (!resultsEqual) break;
- }
-
- Console.WriteLine($"结果相同: {resultsEqual}");
- Console.WriteLine($"加速比: {(double)serialStopwatch.ElapsedMilliseconds / parallelStopwatch.ElapsedMilliseconds:F2}x");
- }
-
- // 生成随机矩阵
- static double[,] GenerateRandomMatrix(int rows, int cols)
- {
- double[,] matrix = new double[rows, cols];
- Random random = new Random();
-
- for (int i = 0; i < rows; i++)
- {
- for (int j = 0; j < cols; j++)
- {
- matrix[i, j] = random.NextDouble() * 100;
- }
- }
-
- return matrix;
- }
-
- // 串行矩阵乘法
- static double[,] SerialMatrixMultiply(double[,] matrixA, double[,] matrixB)
- {
- int aRows = matrixA.GetLength(0);
- int aCols = matrixA.GetLength(1);
- int bCols = matrixB.GetLength(1);
-
- double[,] result = new double[aRows, bCols];
-
- for (int i = 0; i < aRows; i++)
- {
- for (int j = 0; j < bCols; j++)
- {
- for (int k = 0; k < aCols; k++)
- {
- result[i, j] += matrixA[i, k] * matrixB[k, j];
- }
- }
- }
-
- return result;
- }
-
- // 并行矩阵乘法
- static double[,] ParallelMatrixMultiply(double[,] matrixA, double[,] matrixB)
- {
- int aRows = matrixA.GetLength(0);
- int aCols = matrixA.GetLength(1);
- int bCols = matrixB.GetLength(1);
-
- double[,] result = new double[aRows, bCols];
-
- // 使用Parallel.For并行计算
- Parallel.For(0, aRows, i =>
- {
- for (int j = 0; j < bCols; j++)
- {
- for (int k = 0; k < aCols; k++)
- {
- result[i, j] += matrixA[i, k] * matrixB[k, j];
- }
- }
- });
-
- return result;
- }
- }
复制代码
7. 多线程编程的最佳实践
7.1 避免死锁
死锁是多线程编程中的常见问题,当两个或多个线程互相等待对方释放资源时,就会发生死锁。
- using System;
- using System.Threading;
- class DeadlockExample
- {
- private static readonly object _lock1 = new object();
- private static readonly object _lock2 = new object();
-
- static void Main()
- {
- // 创建两个线程,它们以不同的顺序获取锁
- Thread thread1 = new Thread(() =>
- {
- Console.WriteLine("线程1: 尝试获取lock1...");
- lock (_lock1)
- {
- Console.WriteLine("线程1: 获取了lock1");
- Thread.Sleep(100); // 增加死锁发生的概率
-
- Console.WriteLine("线程1: 尝试获取lock2...");
- lock (_lock2)
- {
- Console.WriteLine("线程1: 获取了lock1和lock2");
- }
- }
- });
-
- Thread thread2 = new Thread(() =>
- {
- Console.WriteLine("线程2: 尝试获取lock2...");
- lock (_lock2)
- {
- Console.WriteLine("线程2: 获取了lock2");
- Thread.Sleep(100); // 增加死锁发生的概率
-
- Console.WriteLine("线程2: 尝试获取lock1...");
- lock (_lock1)
- {
- Console.WriteLine("线程2: 获取了lock1和lock2");
- }
- }
- });
-
- thread1.Start();
- thread2.Start();
-
- // 设置超时,防止程序无限期等待
- if (!thread1.Join(5000))
- {
- Console.WriteLine("线程1可能已死锁");
- }
-
- if (!thread2.Join(5000))
- {
- Console.WriteLine("线程2可能已死锁");
- }
-
- Console.WriteLine("主线程退出");
- }
- }
复制代码
避免死锁的最佳实践:
1. 始终以相同的顺序获取多个锁。
2. 使用Monitor.TryEnter而不是lock语句,并指定超时。
3. 尽量减少锁的持有时间。
4. 使用更高级的同步机制,如Mutex、Semaphore等。
7.2 减少锁的粒度
锁的粒度指的是被锁保护的代码范围或数据范围。减少锁的粒度可以提高并发性能。
7.3 使用不可变类型
不可变对象一旦创建就不能被修改,因此在多线程环境中使用不可变类型可以避免同步问题。
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Immutable;
- using System.Threading;
- using System.Threading.Tasks;
- class ImmutableExample
- {
- // 可变类型示例
- class MutablePerson
- {
- public string Name { get; set; }
- public int Age { get; set; }
-
- public MutablePerson(string name, int age)
- {
- Name = name;
- Age = age;
- }
- }
-
- // 不可变类型示例
- class ImmutablePerson
- {
- public string Name { get; }
- public int Age { get; }
-
- public ImmutablePerson(string name, int age)
- {
- Name = name;
- Age = age;
- }
-
- // 返回一个新的不可变对象,而不是修改当前对象
- public ImmutablePerson WithName(string name)
- {
- return new ImmutablePerson(name, Age);
- }
-
- public ImmutablePerson WithAge(int age)
- {
- return new ImmutablePerson(Name, age);
- }
- }
-
- static void Main()
- {
- // 使用可变类型的线程安全问题
- Console.WriteLine("可变类型示例:");
- var mutablePerson = new MutablePerson("Alice", 30);
- var mutableResults = new ConcurrentBag<string>();
-
- Parallel.For(0, 10000, i =>
- {
- // 读取和修改可变对象
- string name = mutablePerson.Name;
- int age = mutablePerson.Age;
-
- // 模拟一些处理
- Thread.Sleep(1);
-
- // 更新对象(可能导致竞争条件)
- mutablePerson.Name = $"{name}_{i}";
- mutablePerson.Age = age + 1;
-
- if (i % 1000 == 0)
- {
- mutableResults.Add($"迭代 {i}: Name={mutablePerson.Name}, Age={mutablePerson.Age}");
- }
- });
-
- Console.WriteLine($"最终结果: Name={mutablePerson.Name}, Age={mutablePerson.Age}");
-
- // 使用不可变类型的线程安全示例
- Console.WriteLine("\n不可变类型示例:");
- var immutablePerson = new ImmutablePerson("Bob", 30);
- var immutableResults = new ConcurrentBag<string>();
-
- Parallel.For(0, 10000, i =>
- {
- // 读取不可变对象
- string name = immutablePerson.Name;
- int age = immutablePerson.Age;
-
- // 模拟一些处理
- Thread.Sleep(1);
-
- // 创建新的不可变对象,而不是修改现有对象
- immutablePerson = immutablePerson.WithName($"{name}_{i}").WithAge(age + 1);
-
- if (i % 1000 == 0)
- {
- immutableResults.Add($"迭代 {i}: Name={immutablePerson.Name}, Age={immutablePerson.Age}");
- }
- });
-
- Console.WriteLine($"最终结果: Name={immutablePerson.Name}, Age={immutablePerson.Age}");
-
- // 使用.NET的不可变集合
- Console.WriteLine("\n不可变集合示例:");
- var immutableList = ImmutableList<int>.Empty;
- var immutableListResults = new ConcurrentBag<string>();
-
- Parallel.For(0, 10000, i =>
- {
- // 创建新的不可变集合,而不是修改现有集合
- immutableList = immutableList.Add(i);
-
- if (i % 1000 == 0)
- {
- immutableListResults.Add($"迭代 {i}: 集合大小={immutableList.Count}");
- }
- });
-
- Console.WriteLine($"最终集合大小: {immutableList.Count}");
- }
- }
复制代码
7.4 避免不必要的线程同步
线程同步会带来性能开销,因此应尽量避免不必要的同步。
7.5 使用CancellationToken取消长时间运行的操作
在多线程编程中,能够取消长时间运行的操作是一项重要功能。
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- class CancellationExample
- {
- static void Main()
- {
- // 创建CancellationTokenSource
- using (var cts = new CancellationTokenSource())
- {
- // 注册回调,在取消时执行
- cts.Token.Register(() =>
- {
- Console.WriteLine("操作已被取消");
- });
-
- // 启动一个可取消的任务
- Task longRunningTask = Task.Run(() => LongRunningOperation(cts.Token), cts.Token);
-
- Console.WriteLine("按C键取消操作,按其他键等待操作完成...");
-
- // 等待用户输入
- if (Console.ReadKey().Key == ConsoleKey.C)
- {
- // 取消操作
- cts.Cancel();
- }
-
- try
- {
- // 等待任务完成
- longRunningTask.Wait();
- }
- catch (AggregateException ex)
- {
- foreach (var innerEx in ex.InnerExceptions)
- {
- if (innerEx is TaskCanceledException)
- {
- Console.WriteLine("任务被成功取消");
- }
- else
- {
- Console.WriteLine($"任务出错: {innerEx.Message}");
- }
- }
- }
- }
-
- Console.WriteLine("主线程退出");
- }
-
- static void LongRunningOperation(CancellationToken cancellationToken)
- {
- Console.WriteLine("长时间运行的操作开始");
-
- try
- {
- for (int i = 0; i < 100; i++)
- {
- // 检查是否已请求取消
- if (cancellationToken.IsCancellationRequested)
- {
- // 抛出OperationCanceledException
- cancellationToken.ThrowIfCancellationRequested();
- }
-
- Console.WriteLine($"处理中: {i + 1}/100");
- Thread.Sleep(100);
- }
-
- Console.WriteLine("长时间运行的操作完成");
- }
- catch (OperationCanceledException)
- {
- // 清理资源
- Console.WriteLine("清理资源...");
- Thread.Sleep(500);
-
- // 重新抛出异常
- throw;
- }
- }
- }
复制代码
8. 总结
本文详细探讨了C#中线程与进程的概念、区别以及多线程编程的应用。我们了解了.NET框架下的并发处理机制,包括同步原语、线程安全集合、并行编程等。通过丰富的代码示例,我们展示了如何在实际开发中应用这些技术来提升程序性能和用户体验。
多线程编程是一项复杂但强大的技术,正确使用它可以显著提高应用程序的性能和响应能力。然而,它也带来了许多挑战,如线程安全、死锁、竞争条件等。遵循最佳实践,如减少锁的粒度、使用不可变类型、避免不必要的同步等,可以帮助我们构建更健壮、更高效的多线程应用程序。
随着.NET框架的不断发展,新的并发编程模式和API不断涌现,如Task Parallel Library (TPL)、Parallel LINQ (PLINQ)、async/await模式等,这些技术使得并发编程变得更加简单和直观。作为C#开发者,掌握这些技术并深入理解其背后的原理,将有助于我们构建更加高效、可靠的应用程序。 |
|