07.并发编程Threads
2022/2/17 20:11:43
本文主要是介绍07.并发编程Threads,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
参考文档
- https://www.cnblogs.com/springsnow/p/9409205.html#_label0
1. 基础概念
1.1 进程/线程/多线程
进程(
Process
)
计算机概念,程序在服务器运行时占据全部计算机资源综合指标,虚拟的数据;普通的解释就是,进程是程序的一次执行
线程(
Thread
)
计算机概念,进程在响应操作时最小单元,也包含CPU/内存/网络/硬盘IO,是虚拟的概念;线程可以理解为进程中的执行的一段程序片段
句柄
操作系统用来标识应用程序的某一个最小单元,比如一个窗体标识,一个按钮标识等,一个long
数字,操作系统通过句柄识别应用程序,也可以通过句柄操作控件
多线程
计算机概念,一个进程有多个线程同时运行
2. C#中的线程(Thread
)
在C#中,Thread
类是操作线程的,是C#对线程的一个封装
说明:这种方式不是特别重要,因为此操作线程方法是.NET Framework 1.0低版本的,只做学习了解即可
2.1 构造函数
线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数),可以用一个类或结构体封装参数
方式一,实例一个无参数,无返回值的线程对象
ThreadStart threadStart = () => Console.WriteLine("无参数无返回值委托"); Thread thread = new Thread(threadStart); thread.Start();
方式二:实例一个有参数,无返回值的线程对象
ParameterizedThreadStart threadStart = (v) => Console.WriteLine("有参数无返回值委托:" + v); Thread thread = new Thread(threadStart); thread.Start("libai");
2.2 属性,方法
静态属性(常用)
属性名 | 属性值类型 | 描述 |
---|---|---|
Thread.CurrentThread | Thread | 获取当前正在运行的线程 |
Thread.CurrentContext | Context | 获取当前线程的上下文 |
实例属性(常用)
属性名 | 属性值类型 | 描述 |
---|---|---|
thread.ThreadState | ThreadState | 获取当前线程状态 |
thread.Priority | ThreadPriority | 获取或设置线程优先级 |
thread.Name | string | 获取或设置线程名称 |
thread.ManagedThreadId | int | 获取线程唯一ID |
thread.IsBackground | bool | 获取或设置线程是否为后台线程 |
实例方法(常用)
方法名 | 返回值类型 | 描述 |
---|---|---|
thread.Start(...) | void | 启动线程 |
thread.Abort(...) | void | 终止线程 |
thread.Join(...) | bool | 等待线程 |
2.3 线程等待方式
方式一:持续判断线程状态,直到线程停止
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { { ThreadStart threadStart = () => Console.WriteLine("无参数无返回值委托"); Thread thread = new Thread(threadStart); thread.Start(); while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(200); } } } } }
方式二:
Join
方法
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { { ThreadStart threadStart = () => Console.WriteLine("无参数无返回值委托"); Thread thread = new Thread(threadStart); thread.Start(); thread.Join(); // 一直等待,直到结束 thread.Join(3000); // 最多等待 3000 毫秒 } } } }
2.4 线程状态值
// 指定的执行状态 public enum ThreadState { // 启动线程 Running = 0, // 正在请求线程停止,仅供内部使用 StopRequested = 1, // 正在请求线程挂起 SuspendRequested = 2, // 该线程将作为后台线程 Background = 4, Unstarted = 8, // 该线程已停止 Stopped = 16, // 线程将受阻 WaitSleepJoin = 32, // 该线程已挂起 Suspended = 64, AbortRequested = 128, Aborted = 256 }
2.5 线程的优先级别
.NET为线程设置Priority
属性来定义线程执行的优先级别,里面包含5个选项,其中Normal
是默认值,除非系统有特殊要求,否则不应该随便设置线程的优先级别;因为线程是计算机概念,执行由操作系统执行,所以这个属性只是增加优先级概率,并不一定按照设置的优先级执行
ThreadStart threadStart = () => Console.WriteLine("无参数无返回值委托"); Thread thread = new Thread(threadStart); thread.Priority = ThreadPriority.Highest; thread.Start();
线程优先级选项:
// 指定的调度优先级的 public enum ThreadPriority { // 可以安排在具有任何其他优先级的线程之 Lowest = 0, // 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前 BelowNormal = 1, // 可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前 Normal = 2, // 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前 AboveNormal = 3, // 可以将 Thread 安排在具有任何其他优先级的线程之前 Highest = 4 }
2.6 线程池(ThreadPool
)
概念说明
由来:在 Thread
中对线程的管理需要手动操作,在不断的开启和销毁中,存在很大的开销,为了让线程反复使用,出现了池化思想;线程池可以节省资源,控制线程总数量,防止滥用
线程池维护一个请求队列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带来的开销
线程池并不是在 CLR 初始化的时候立刻创建线程的,而是在应用程序要创建线程来执行任务的时候,线程池才会初始化一个线程,初始化的线程和其他线程一样,但是在线程完成任务之后不会自行销毁,而是以挂起的状态回到线程池。当应用程序再次向现成池发出请求的时候,线程池里挂起的线程会再度激活执行任务。这样做可以减少线程创建和销毁所带来的开销
在C#中,提供了操作线程池的静态帮助类ThreadPool
,并提供一些静态方法操作线程池
静态方法(常用)
方法 | 返回值类型 | 描述 |
---|---|---|
ThreadPool.QueueUserWorkItem(...) | bool | 可以理解为开启一个线程,成功true |
ThreadPool.GetMaxThreads(...) | void | 获取本机最大辅助,异步I/O线程数 |
ThreadPool.GetMinThreads(...) | void | 获取本机最小辅助,异步I/O线程数 |
ThreadPool.SetMaxThreads(...) | bool | 设置本机最大辅助,异步I/O线程数 |
ThreadPool.SetMinThreads(...) | bool | 设置本机最小辅助,异步I/O线程数 |
代码示例
示例一:开启一个线程
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { ThreadPool.QueueUserWorkItem(o=> { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00")); Tesk(); }); Thread.Sleep(2000); } static void Tesk() { Console.WriteLine("开启了一个线程"); } } }
示例二:开启一个线程并传入参数
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { ThreadPool.QueueUserWorkItem(o => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00")); Tesk(o); }, "libai"); Thread.Sleep(2000); } static void Tesk(object value) { Console.WriteLine("开启了一个线程,参数:" + value); } } }
示例三:获取线程池中辅助线程,异步 I/O 线程的最大,最小数目
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads); ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); Console.WriteLine($"辅助线程数,最大【{maxWorkerThreads}】,最小【{minWorkerThreads}】"); Console.WriteLine($"异步I/O线程数,最大【{maxCompletionPortThreads}】,最小【{minCompletionPortThreads}】"); } }
示例四:设置线程池最大最小线程数(这个设置是全局的)
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { ThreadPool.SetMaxThreads(100, 100); // 设置最大数量时不能低于本机线程数,否则设置无效 ThreadPool.SetMinThreads(1, 2); ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads); ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); Console.WriteLine($"辅助线程数,最大【{maxWorkerThreads}】,最小【{minWorkerThreads}】"); Console.WriteLine($"异步I/O线程数,最大【{maxCompletionPortThreads}】,最小【{minCompletionPortThreads}】"); } } }
3. Task任务
3.1 简述说明
Task
对象
Task
对象是一种的中心思想基于任务的异步模式首次引入.NET Framework 3
中,表示一个任务
System.Threading.Tasks.Task
类是Task Programming Library(TPL)
中最核心的一个类
注意的是,Task
里面的线程是来自线程池的
任务与线程
任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行,但是任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制
如下图所示,任务最后是托管到线程上执行的
3.2 创建方式
创建Task
有三种方式,使用构造函数创建,使用Task.Run
创建,使用 Task.Factory.StartNew
进行创建,这三种方式都是一样的,不过Task.Factory
是对Task
进行管理,调度管理这一类的
方式一:使用构造函数创建
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { Task task1 = new Task(() => { Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); task1.Start(); Thread.Sleep(2000); } } }
方式二:使用
Task.Run
创建
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { Task task1 = Task.Run(() => { Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); Thread.Sleep(2000); } } }
方式三:使用
Task.Factory.StartNew
进行创建并运行
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { // 方式一:使用静态工厂属性 { Task task1 = Task.Factory.StartNew(() => { Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); } // 方式二:使用实例工厂 { TaskFactory factory = new TaskFactory(); Task task1 = factory.StartNew(() => { Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); } Thread.Sleep(2000); } } }
重载参数补充说明
在创建Task
的时候,Task
有很多的构造函数的重载,一个主要的重载就是传入TaskCreateOptions
的枚举
TaskCreateOptions.None
:用默认的方式创建一个Task
TaskCreateOptions.PreferFairness
:请求scheduler
尽量公平的执行Task
(优先级)TaskCreateOptions.LongRunning
:声明Task
将会长时间的运行TaskCreateOptions.AttachToParent
:因为Task
是可以嵌套的,所以这个枚举就是把一个子task
附加到一个父task
中
3.3 属性,方法
实例属性(常用)
属性名 | 属性值类型 | 描述 |
---|---|---|
task1.Id | int | 获取此Task 实例的 ID |
task1.IsCompleted | bool | 获取此Task 是否已完成 |
task1.Status | TaskStatus | 获取此任务的任务状态 |
runTask.Result | T | 获取任务返回值 |
实例方法(常用)
方法名 | 返回值类型 | 描述 |
---|---|---|
task1.Start(...) | void | 启动,开启一个线程 |
task1.ContinueWith(...) | Task | 创建一个在目标Task 完成时异步执行的延续任务 |
task1.Wait(...); | bool | 等待任务完成 |
静态方法(常用)
方法名 | 返回值类型 | 描述 |
---|---|---|
Task.WaitAny(...) | int | 等待提供的任一 Task 对象完成执行过程 |
Task.WaitAll(...) | void | 等待提供的所有Task 对象完成执行过程 |
代码示例
示例一:WaitAny
,WaitAll
方法示例(结果会受计算机性能影响,建议执行多次)
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { { Task task1 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); Task task2 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); task1.Start(); task2.Start(); Task.WaitAny(task1, task2); Console.WriteLine("执行完成WaitAny"); Thread.Sleep(2000); } Console.WriteLine("============================================"); { Task task1 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); Task task2 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); task1.Start(); task2.Start(); Task.WaitAll(task1, task2); Console.WriteLine("执行完成WaitAll"); Thread.Sleep(2000); } } } }
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { { Task task1 = new Task(() => { Thread.Sleep(3000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); Task task2 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); task1.Start(); task2.Start(); Task.WaitAny(task1, task2); Console.WriteLine("有一个执行结束"); Task.WaitAll(task1, task2); Console.WriteLine("全部执行结束"); } } } }
3.4 常用示例
1. 示例:连续任务
所谓的延续的Task
就是在第一个Task
完成后自动启动下一个Task
,参数为上次Task
的引用
示例一:单个连续任务
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { { Task task1 = new Task(() => { Thread.Sleep(2000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); Task task2 = new Task(() => { Thread.Sleep(1000); Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00")); }); task1.ContinueWith(t=> { task2.Start(); }); task1.Start(); Thread.Sleep(5000); } } } }
示例二:多个连续任务
using System; using System.Threading; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { var SendTask = Task.Factory.StartNew(() => Console.WriteLine("无参数")) .ContinueWith<bool>(s => { return true; }) .ContinueWith<string>(r => { Console.WriteLine(r.Result); return "libai"; }); Console.WriteLine(SendTask.Result); } } }
2. 示例:任务结果
示例一:单个任务结果
using System; using System.Threading.Tasks; namespace th3 { class Program { static void Main(string[] args) { // 方式一 { Task<int> task = new Task<int>(()=> { return 1; }); task.Start(); Console.WriteLine(task.Result); } // 方式二 { Task<int> task = Task.Run<int>(()=> { return 1; }); Console.WriteLine(task.Result); } } } }
3.5 线程同步
所谓同步:是指在某一时刻只有一个线程可以访问变量
c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock
,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待
关键字
Lock
定义
expression
代表你希望跟踪的对象:- 如果你想保护一个类的实例,一般地,你可以使用
this
- 如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可
- 如果需要锁变量,推荐使用
object
类型
- 如果你想保护一个类的实例,一般地,你可以使用
statement_block
就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行
Lock(expression) { statement_block }
示例代码
namespace T1 { class Program { static void Main(string[] args) { BookShop book = new BookShop(); //创建两个线程同时访问Sale方法 Thread t1 = new Thread(new ThreadStart(book.Sale)); Thread t2 = new Thread(new ThreadStart(book.Sale)); //启动线程 t1.Start(); t2.Start(); Console.ReadKey(); } } class BookShop { //剩余图书数量 public int num = 1; public void Sale() { //使用lock关键字解决线程同步问题 lock (this) { int tmp = num; if (tmp > 0)//判断是否有书,如果有就可以卖 { Thread.Sleep(1000); num -= 1; Console.WriteLine("售出一本图书,还剩余{0}本", num); } else { Console.WriteLine("没有了"); } } } } }
4. async/await
.NET 4.5版本时,C#提供Async
和Await
关键字(语法糖)来实现异步编程,两者是一组关键子,通常一起使用
Async
是用来修饰方法,Await
在方法内部,只能方法Async
修饰的方法内或者Task
前面
作用:用同步方法的思维写异步方法,提高了内存占用率
官方介绍
-
await
运算符暂停对封闭async
方法的求值,直到其操作数表示的异步操作完成 -
异步操作完成后,
await
运算符将返回操作的结果(如果有) -
当
await
运算符应用到表示已完成操作的操作数时,它将立即返回操作的结果,而不会暂停封闭的方法 -
await
运算符不会阻止计算异步方法的线程 -
若某个函数
F
的函数体中需要使用await
关键字的函数必须以async
标记
4.1 代码示例
示例一:执行顺序
using System; using System.Threading; using System.Threading.Tasks; namespace th4 { class Program { static void Main(string[] args) { Show(); Console.ReadKey(); } static void Show() { Console.WriteLine("1\t" + Thread.CurrentThread.ManagedThreadId.ToString("00")); // 1.首先执行主线程 Async(); // 2.2.调用后返回主线程 Console.WriteLine("2\t" + Thread.CurrentThread.ManagedThreadId.ToString("00")); // 3.主线程执行 } static async void Async() { Console.WriteLine("5\t" + Thread.CurrentThread.ManagedThreadId.ToString("00")); // 2.1.按照顺序执行,也是主线程 await Task.Run(() => // 4.开启子线程执行,使用await等待完成 { Thread.Sleep(200); Console.WriteLine("3\t" + Thread.CurrentThread.ManagedThreadId.ToString("00")); // 5.子线程执行 }); Console.WriteLine("4\t" + Thread.CurrentThread.ManagedThreadId.ToString("00")); // 6.等待子线程完成后执行 } } }
5. 扩展补充
5.1 为什么可以多线程
-
多个CPU的核可以并行工作,几核几线程,这里的线程指的是模拟核并行;多核之间叫并行
-
CPU分片,1秒的处理能力分成1000份,操作系统调度着去响应不同的任务,从宏观角度来说,感觉就是多个任务在并行执行,从微观角度来说,一个物理CPU同一时刻只能为一个任务服务,并发:CPU分片的并发
5.2 同步异步特点
可等待
同步方法:主线程(UI线程)忙于计算,无暇其它(卡界面)
异步多线程方法:主线程闲置,计算任务交给子线程完成(不卡界面)
执行速度
同步方法:慢,因为只有一个线程计算
异步多线程方法:快,因为多个线程并发计算,CPU利用率高(密集型计算),以资源换性能
执行顺序
同步方法:有序执行
异步多线程方法:启动无序,线程资源是向操作系统申请的,由操纵系统的调度策略决定,所以启动顺序随机;同一个任务同一个线程,执行时间也不确定,CPU分片结束也无序
5.3 如何实现线程有序执行?
控制线程执行顺序有四种方法可实现
方法一:使用委托回调,回调方法是在子线程中执行
using System; namespace th2 { class Program { static void Main(string[] args) { Action<string> action = Tesk1; action.Invoke("libai"); Console.WriteLine("end"); AsyncCallback callback = re => { Console.WriteLine("end"); }; action.BeginInvoke("zhaoyun",callback,null); } static void Tesk1(string key) { Console.WriteLine("start:" + key); } } }
5.4 线程/进程的区别
-
进程间是独立的,这表现在内存空间,上下文环境;
线程运行在进程空间内,进程包含线程(包含关系)
-
进程是无法突破进程存取其他进程内的存储空间;
而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
-
同一进程中的两段代码不能够同时执行,除非引入线程
-
线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除
5.5 前台线程,后台线程
通过设置线程的IsBackground
属性,设置线程为前台或后台线程,通常用在WinFrom
程序
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { { ThreadStart threadStart = () => Console.WriteLine("无参数无返回值委托"); Thread thread = new Thread(threadStart); thread.IsBackground = true; // 后台线程,进程结束,线程结束 thread.IsBackground = false; // 前台线程,进程结束,等待线程执行结束 thread.Start(); } } } }
6. 应用示例
6.1 示例一:封装线程回调
需求:封装一个方法,当子线程执行结束后,调用回调方法
思路:首先肯定是需要一个子线程来执行的,其次回调方法必须是线程要执行的内容结束后调用;所以可以在其外部加一层,因为子线程是不卡主线程的,可以在子线程内部依次等待调用
using System; using System.Threading; namespace th3 { class Program { static void Main(string[] args) { { ThreadStart threadStart = () => { Console.WriteLine("开始执行"); Thread.Sleep(2000); }; Action action = () => { Console.WriteLine("执行结束"); }; ThreadWithCallBack(threadStart, action); } } static void ThreadWithCallBack(ThreadStart threadStart, Action callback) { ThreadStart start = () => { threadStart.Invoke(); callback.Invoke(); }; Thread thread = new Thread(start); thread.Start(); } } }
这篇关于07.并发编程Threads的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-28一步到位:购买适合 SEO 的域名全攻略
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门