C#多线程基本使用和探讨
线程是并发编程的基础概念之一。在现代应用程序中,我们通常需要执行多个任务并行处理,以提高性能。C# 提供了多种并发编程工具,如
Thread、Task、异步编程和Parallel等。
Thread 类
Thread 类是最基本的线程实现方法。使用Thread类,我们可以创建并管理独立的线程来执行任务。
基本使用Thread
创建一个新的实例对象,将一个方法直接给Thread类,并使用实例对象启动线程运行。
static void Test() {Console.WriteLine("Test事件开始执行");Thread.Sleep(1000);Console.WriteLine("Test事件睡眠之后打印数据");Console.WriteLine("Test事件id: " + Thread.CurrentThread.ManagedThreadId);}static void Main(string[] args){#region //主线程idConsole.WriteLine("主线程id: " + Thread.CurrentThread.ManagedThreadId);#endregion#region //传递一个方法给线程,执行方法Thread t = new Thread(Test);t.Start();#endregion
}
线程数据传输
传递单个参数
Thread 提供了一个 Start(object parameter) 方法,可以在启动线程时传递一个参数。
static int Downlod(object obj) {string str = obj as string;Console.WriteLine(str);}static void Main(string[] args){Thread t1 = new Thread(Downlod);//这里传递的参数是字符串t1.Start("数据传递方法执行 ,数据传递方法id: "+Thread.CurrentThread.ManagedThreadId);}
这种方式适用于需要传递单个参数的情况。
使用类的实例方法传递多个参数
先new一个类的实例,给实例字段赋值,调用类的实例,通过实例调用方法,构造函数接收参数,然后在线程中调用该实例的方法。
static void Main(string[] args){
// 使用 DownloadTool 实例化并传递数据
DownloadTool downloadTool = new DownloadTool("www.baidu.com", "这是下载链接哦");
Thread thread = new Thread(downloadTool.Download);
thread.Start();
}public class DownloadTool
{private string url;private string message;public DownloadTool(string url, string message){this.url = url;this.message = message;}public void Download(){Console.WriteLine("下载链接: " + url);Console.WriteLine("提示信息: " + message);Console.WriteLine("Download 线程 ID: " + Thread.CurrentThread.ManagedThreadId);}
}
当 thread 线程启动时,它会执行 downloadTool.Download() 方法,输出传递的数据。
线程优先级
在 C# 中,可以使用 Thread.Priority 属性来设置线程的优先级。线程优先级决定了操作系统在多线程环境中调度线程的顺序,但并不保证高优先级的线程总是比低优先级的线程更早或更频繁地执行。
线程优先级级别
C# 提供了五个线程优先级级别,定义在 ThreadPriority 枚举中:
- Lowest:最低优先级。操作系统尽可能少地调度这个优先级的线程。
- BelowNormal:低于正常的优先级。优先级比 Normal 低,但高于 Lowest。
- Normal:默认优先级,大多数线程默认的优先级。适用于一般用途。
- AboveNormal:高于正常的优先级。操作系统更倾向于调度这个优先级的线程。
- Highest:最高优先级。操作系统尽可能多地调度这个优先级的线程。
internal class Program{static void A(){int i = 0;while (true){i++;Console.WriteLine($"A 输出第{i}");Thread.Sleep(1000);}}static void B(){int i = 0;while (true){i++;Console.WriteLine($"B 输出第{i}");Thread.Sleep(1000);}}static void Main(string[] args){//在C#中,线程的优先级可以通过Thread.Priority属性来设置和获取。
// Lowest: 线程的优先级是最低的。在系统中存在其他活动线程时,此优先级的线程很少得到执行。
//BelowNormal: 线程的优先级低于正常线程。
//Normal: 线程的优先级是普通的,这是线程的默认优先级。
//AboveNormal: 线程的优先级高于正常线程。
//Highest: 线程的优先级是最高的。此优先级的线程会尽量优先于其他所有优先级的线程执行。Thread a = new Thread(A);Thread b = new Thread(B);a.Priority = ThreadPriority.Highest;a.Start();b.Priority = ThreadPriority.Lowest;b.Start();Console.WriteLine("按任意键停止线程...");Console.ReadKey();a.Join();b.Join();Console.WriteLine("线程已停止");}}
A被设置为最高优先级ThreadPriority.Highest。B被设置为最低优先级ThreadPriority.Lowest。
注意事项
-
优先级不是绝对控制:操作系统可能会忽略优先级设置,特别是在资源有限的系统中。高优先级线程不一定会一直执行,也不能阻止低优先级线程的执行。
-
使用优先级的适用场景:设置线程优先级可能适用于实时系统(例如,某些任务需要优先处理)。但是,大多数应用程序通常可以使用默认的
Normal优先级。 -
避免使用过多的高优先级线程:如果所有线程都被设置为
Highest,系统的整体性能可能会下降,甚至导致线程争用 CPU 资源的情况。 -
CPU 密集型任务:在 CPU 密集型任务中,优先级可能会对性能产生较大影响,因为优先级高的线程可能会占用更多的 CPU 时间。
线程优先级的最佳实践
- 默认使用
Normal优先级,除非有特殊原因。 - 避免滥用
Highest优先级,因为它会对系统资源产生影响。 - 在 I/O 密集型的线程中,优先级通常不会有显著差异,因为这些线程在等待 I/O 操作完成时,CPU 会调度其他线程。
通过合理设置线程优先级,可以帮助操作系统更好地调度线程,以满足应用程序的需求。 但通常在 .NET 应用程序中,多数情况下使用默认的 Normal 优先级就足够了。
线程池
线程池 (ThreadPool) 是一种高效的管理和调度线程的方式。线程池自动管理线程的创建、重用和销毁,从而减少了手动创建和管理线程的开销。
为什么使用线程池
- 性能更高:线程池会重用现有的线程,减少了创建和销毁线程的开销。
- 自动管理:线程池会根据系统负载动态调整线程数量。
- 避免线程资源不足:线程池限制了同时运行的线程数,避免了线程过多导致的资源耗尽问题。
基本使用 ThreadPool.QueueUserWorkItem 方法将任务排入线程池队列。
using System;
using System.Threading;class Program
{static void Main(string[] args){// 将任务排入线程池ThreadPool.QueueUserWorkItem(DoWork, "任务 1");ThreadPool.QueueUserWorkItem(DoWork, "任务 2");Console.WriteLine("主线程完成");Thread.Sleep(3000); // 等待线程池中的任务完成}static void DoWork(object state){string taskName = (string)state;Console.WriteLine($"{taskName} 开始执行 - 线程ID: {Thread.CurrentThread.ManagedThreadId}");Thread.Sleep(1000); // 模拟耗时操作Console.WriteLine($"{taskName} 执行完成 - 线程ID: {Thread.CurrentThread.ManagedThreadId}");}
}
运行结果QueueUserWorkItem自动分配线程来执行任务。

QueueUserWorkItem 方法将任务排入线程池。它接收一个委托(即方法)和一个可选的状态对象(传递给方法的数据)。
DoWork 方法接受一个参数 state,这是从 QueueUserWorkItem 传递的。
Thread.Sleep(3000) 确保主线程不会立即退出,使得线程池中的任务有机会完成。
使用带返回值的线程池任务
C# 中的 ThreadPool 通常不直接支持返回值。如果需要获得任务结果,可以使用 Task,因为 Task 本质上也是线程池的一部分。Task 更适合于带返回值的异步操作。这里使用 Task.Run 来代替 ThreadPool:
static void Main(string[] args){//使用tesk多线程int a= Task.Run(() =>{int a = Dowload();return a;}).Result;Task<int> task = Task<int>.Run(()=>{int a = Dowload();return a;});//初始化一个CancellationTokenSource实例CancellationTokenSource source = new CancellationTokenSource();//task.Start();task.Wait(1000);source.Cancel();int result = task.Result;Console.WriteLine(result);Console.WriteLine($"tesk返回值{a}");
}static int Dowload() {int a = 0;for (int i = 0; i < 10; i++){a= a + i + 1;}int? id= Task.CurrentId;Console.WriteLine("Current thread ID: " + id);return a;}
线程池的限制
- 任务运行时间过长:线程池中的线程本质上是共享资源,如果某个任务运行时间太长,将会占用线程池中的线程,导致其他任务无法及时执行。
- 不适合实时系统:线程池中的任务调度是由系统管理的,无法保证精确的实时性。
- 有限的线程数量:在高并发场景中,如果线程池中的线程全部被占用,新的任务将会等待,直到有线程可用。
线程池总结
线程池是一种高效的并发处理方式,适合于大多数轻量级的后台任务。在现代 C# 编程中,建议使用 Task 和 async/await 进行异步操作,因为它们能简化代码,并且使用底层的线程池来管理线程。如果需要精确控制线程的执行,通常建议使用手动管理的 Thread 等。
线程锁
使用多线程,在多线程编程中,如果多个线程同时访问和修改共享资源(如全局变量、文件、数据库等),可能会导致数据不一致或竞争条件。为了避免这种情况,多线程锁通过控制对共享资源的访问来保证线程安全性。
资源冲突示例:
static void Main(string[] args){//调用方法循环创建新的线程来执行方法StateObject state = new StateObject();for (int i = 0; i < 30; i++){Thread thread = new Thread(state.ChangState);thread.Start();}Console.ReadKey();}//一个StateObject类public class StateObject{private int state = 5;
//里面有个状态改变方法,当状态等于5的时候进入到方法中,然后state+1 打印的应该是6 和线程idpublic void ChangState(){if (state == 5){state++;Console.WriteLine($"state:{state} 线程id:" + Thread.CurrentThread.ManagedThreadId);}state = 5;}}
运行结果:
因为资源冲突的原因,有一些线程执行的时候,可能另外一个线程没有执行完,另外一个线程就已经进入到方法里面了。因为有修改的操作导致State状态混乱,资源冲突。这时候我们就需要一个东西来维护,让线程执行指定方法时一个一个的执行,不会冲突。

简单使用lock锁
1.创建一个private readonly object lockObject = new object(); // 锁对象
2.使用lock (lockObject){
//业务代码,执行到有修改的操作的代码
} // 使用锁对象来确保线程安全
static void Main(string[] args){StateObject state = new StateObject();for (int i = 0; i < 100; i++){Thread thread = new Thread(state.ChangState);thread.Start();}Console.ReadKey();}public class StateObject{private object _lock = new object();private int state = 5;public void ChangState(){//使用锁保证每次执行方法的都是一个线程,防止资源冲突lock (_lock){if (state == 5){state++;Console.WriteLine($"state:{state} 线程id:" + Thread.CurrentThread.ManagedThreadId);}state = 5;}}}
这样运行起来就能把每个线程操作隔离开,保证state的状态不会冲突。
为什么要创建一个 object 对象作为锁?
- 专用性:创建一个专用的锁对象(如
private readonly object lockObject = new object();),可以确保锁定的是特定的同步逻辑,而不是其他对象。这有助于避免意外的锁冲突或死锁。 - 避免使用其他共享对象:虽然可以使用任意的引用类型对象作为锁对象(包括
this或Type对象),但这可能会带来不必要的风险,尤其是在public方法或对象中,这样可能会导致意外的锁定冲突。
常见问题死锁:
锁并不是万能,也不是有锁就是最好,要看情况使用,锁也会产生问题,常见的问题就是死锁等问题。
演示死锁:
thread1方法1 的锁里面嵌套锁住了thread2的锁,thread2方法2的锁里面嵌套锁住了thread1的锁,这种锁与锁嵌套使用,就是容易出问题。导致线程锁死程序无法动弹。
static void Main(string[] args){DeadlockExample deadlockExample = new DeadlockExample();Thread t1 = new Thread(deadlockExample.Thread1);Thread t2 = new Thread(deadlockExample.Thread2);t1.Start();t2.Start();t1.Join();t2.Join();Console.ReadKey();}}public class DeadlockExample{private static object lock1 = new object();private static object lock2 = new object();public void Thread1(){lock (lock1){Console.WriteLine("线程1:已获取锁1,正在等待锁2。。。");Thread.Sleep(100); // 模拟某些工作lock (lock2){Console.WriteLine("线程1:获得锁2");}}}public void Thread2(){lock (lock2){Console.WriteLine("线程2:已获取锁2,正在等待锁1。。。");Thread.Sleep(100); // 模拟某些工作lock (lock1){Console.WriteLine("线程2:获得锁1");}}}}
运行结果:
死锁发生在两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行。

总结:
多线程锁在 C# 中主要用于解决以下问题:
- 竞态条件:通过锁机制防止多个线程同时访问和修改共享资源,确保数据一致性。
- 死锁:防止多个线程相互等待资源,通过锁的顺序或者避免嵌套锁来解决。
- 资源饥饿:确保每个线程都能获取资源,使用
Monitor.TryEnter等机制防止无限等待。 - 读写锁:允许多个线程并发读取资源,但写入时互斥,适合读多写少的场景。
C# 提供了多种锁机制,开发者可以根据应用场景选择合适的锁类型。
如果不想使用 lock 关键字,C# 还提供了其他锁机制,比如 Mutex、Semaphore、Monitor 等
相关文章:
C#多线程基本使用和探讨
线程是并发编程的基础概念之一。在现代应用程序中,我们通常需要执行多个任务并行处理,以提高性能。C# 提供了多种并发编程工具,如Thread、Task、异步编程和Parallel等。 Thread 类 Thread 类是最基本的线程实现方法。使用Thread类࿰…...
PHP DateTime基础用法
PHP DateTime 的用法详解 一、引言 在开发 PHP 应用程序时,处理日期和时间是一个至关重要的任务。PHP 提供了强大的日期和时间处理功能,其中 DateTime 类是最常用的工具之一。DateTime 类提供了丰富的方法来创建、格式化、计算和比较日期时间ÿ…...
一次Fegin CPU占用过高导致的事故
记录一下 一次应用事故分析、排查、处理 背景介绍 9号上午收到CPU告警,同时业务反馈依赖该服务的上游服务接口响应耗时太长 应用告警-CPU使用率 告警变更 【WARNING】项目XXX,集群qd-aliyun,分区bbbb-prod,应用customer,实例customer-6fb6448688-m47jz, POD实例CP…...
【Go初阶】两万字快速入门Go语言
初见golang语法 package mainimport "fmt"func main() {/* 简单的程序 万能的hello world */fmt.Println("Hello Go")} 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main…...
【React】使用 react hooks 需要遵守的原则
1)只能在顶层调用Hooks 这是指你不能在循环、条件语句或嵌套函数中调用Hooks。确保每次组件渲染时,Hooks的调用顺序保持一致。因此,你应该始终在React函数组件的最顶层调用Hooks。 React依赖于Hooks的调用顺序。如果这些调用在不同的渲染中顺…...
Python编程:创意爱心表白代码集
在寻找一种特别的方式来表达你的爱意吗?使用Python编程,你可以创造出独一无二的爱心图案,为你的表白增添一份特别的浪漫。这里为你精选了六种不同风格的爱心表白代码,让你的创意和情感通过代码展现出来。 话不多说,咱…...
腾讯IM SDK:TUIKit发送多张图片
一、问题描述 在使用腾讯IM DEMO(https://github.com/TencentCloud/chat-uikit-vue.git)时发现其只支持发送一张图片: 二、解决方案 // src\TUIKit\components\TUIChat\message-input-toolbar\image-upload\index.vue<inputref"inp…...
《本地部署开源大模型》在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战
在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战 无论是在单机单卡(一台机器上只有一块GPU)还是单机多卡(一台机器上有多块GPU)的硬件配置上启动ChatGLM3-6B模型,其前置环境配置和项目文件是相同的。如果大家对配置过程还…...
Python 脚本来自动发送每日电子邮件报告
安装必要的库 我们将使用 smtplib 发送邮件,以及 email.mime 来创建电子邮件内容。另外,为了让脚本自动定时运行,可以使用操作系统的计划任务工具(如 Linux 的 cron 或 Windows 的 Task Scheduler)。 创建邮件内容 使…...
大语言模型与ChatGPT:深入探索与应用
文章目录 1. 前言2. 大语言模型的概述2.1 什么是大语言模型?2.2 Transformer架构的核心2.3 预训练与微调 3. ChatGPT的架构与技术背景3.1 GPT模型的演进3.2 ChatGPT的工作原理 4. ChatGPT的实际应用4.1 日常对话助手4.2 内容生成与写作4.3 编程辅助4.4 教育与学习辅…...
【从零开始的LeetCode-算法】3164.优质数对的总数 II
给你两个整数数组 nums1 和 nums2,长度分别为 n 和 m。同时给你一个正整数 k。 如果 nums1[i] 可以被 nums2[j] * k 整除,则称数对 (i, j) 为 优质数对(0 < i < n - 1, 0 < j < m - 1)。 返回 优质数对 的总数。 示…...
FastDFS VS MinIO:文件存储与对象存储的抉择(包含SpringBoot集成FastDFS范例)
FastDFS vs MinIO:文件存储与对象存储的抉择(包含SpringBoot集成FastDFS范例) 我坐在窗边,随着飞机穿过云层,在云层之上滑翔。可以清晰的看到飞机在天空留下的痕迹,不知道那是蔚蓝中的纯白,还是…...
【Redis】缓存预热、雪崩、击穿、穿透、过期删除策略、内存淘汰策略
Redis常见问题总结: Redis常见问题总结Redis缓存预热Redis缓存雪崩Redis缓存击穿Redis缓存穿透 Redis 中 key 的过期删除策略数据删除策略 Redis内存淘汰策略一、Redis对过期数据的处理(一)相关配置(二)内存淘汰流程&a…...
【LeetCode】每日一题 2024_10_15 三角形的最大高度(枚举、模拟)
前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动! 题目:三角形的最大高度 代码与解题思路 久违的简单题 这道题读完题目其实不难想到有两条路可以走: 1、题目很明显只有两种情况,枚举是第一个球是红球还是蓝球这两种情…...
2024版最新网络安全工程师入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
前言 想要成为网络安全工程师,却苦于没有方向,不知道从何学起的话,下面这篇 网络安全入门 教程可以帮你实现自己的网络安全工程师梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到…...
vue中关于router.beforeEach()的用法
router.beforeEach()是Vue.js中的路由守卫,用于在路由跳转前进行校验、取消、重定向等操作。 基本使用: const router new VueRouter({ ... })router.beforeEach((to, from, next) > {// ... }) to: 即将要进入的目标路由对象 from: 当前导航正要…...
C++模板初阶,只需稍微学习;直接起飞;泛型编程
🤓泛型编程 假设像以前交换两个函数需要,函数写很多个或者要重载很多个;那么有什么办法实现一个通用的函数呢? void Swap(int& x, int& y) {int tmp x;x y;y tmp; } void Swap(double& x, double& y) {doubl…...
【数据结构 | 红黑树】红黑树的性质和插入结点时的调整
文章目录 红黑树红黑树插入时的调整?1. 插入结点是根结点2. 插入结点的叔叔是红色3. 插入结点的叔叔是黑色LL 型RR型LR型RL型 红黑树 前提:二叉搜索树(左 < 根 < 右)—— 左根右根和**叶子(NULL)**都…...
mysql学习教程,从入门到精通,SQL导入数据(44)
1.SQL 导出数据 以下是一个关于如何使用 SQL 导出数据的示例。这个示例将涵盖从一个关系数据库管理系统(如 MySQL)中导出数据到 CSV 文件的基本步骤。 1.1、前提条件 你已经安装并配置好了 MySQL 数据库。你有访问数据库的权限。你知道要导出的表名。…...
【SpringAI】(二)让你的Java程序接入大模型——适合Java宝宝的大模型应用开发
开始之前,如果你对大模型完全没了解过,建议阅读之前的大模型入门文章: 【SpringAI】(一)从实际场景入门大模型——适合Java宝宝的大模型应用开发 那么今天就开始写一个基于Spring AI程序的HelloWord!将大模型接入到咱…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
