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!将大模型接入到咱…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
