马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
概述
进程和线程
进程:指在体系中运行的一个应用程序。
线程:进程中的一个实验任务。一个进程至少有一个线程,一个进程可以有多个线程,多个线程可共享数据。
多线程
多线程:在一个程序中同时运行多个线程,每个线程实验各自的任务。
长处:利用多线程可以提高应用程序的响应本领,并利用多处理器或多核体系提高应用程序吞吐量。
缺点:死锁和争用条件
多线程适用场景:任务实验比较耗时的情况,也可以解决一些非常耗时的且长时间占用cpu资源的程序。
多线程的特点:
1、运行顺序不确定。
2、线程之间平行实验。
前台线程和配景线程
前台线程必须全部实验完,即使主线程关闭掉,这时进程仍旧存活。配景线程在未实验完成时,如果前台线程关掉,则配景线程也会停掉。配景线程会随着主线程的关闭,而主动关闭。
增补
1、新创建的Thread默认是前台线程,可以通过设置IsBackground属性将其改为配景线程
2、线程池中的线程是配景线程
3、Task开启的线程是配景线程
4、前台线程适用场合:紧张焦点的,大概需要长时间等待的任务,比方:UI界面线程、发送数据的线程
5、配景线程适用:非焦点且用于处理时间较短的任务适用。
Thread
Thread开启的线程默认都是配景线程
开启线程
- //命名空间
- using System.Threading;
- Thread thread = new Thread(SayHi);
- void SayHi()
- {
- Thread.Sleep(3000);
- Console.WriteLine("Hello World!");
- }
- thread.Start();
复制代码 线程传参
- //传递单个参数,thread.Start只支持传一个参数
- Thread thread = new Thread((fileName) =>
- {
- Console.WriteLine($"正在下载的文件名是{fileName}");
- });
- thread.Start("原神.apk");
- //传递多个参数 定义一个专门类,通过构造函数传参
- //自定义类
- class WriteInfo
- {
- private string _name;
- private int _age;
- public WriteInfo(string name, int age)
- {
- _name = name;
- _age = age;
- }
- public void ShowHumanInfo()
- {
- Console.WriteLine($"我叫{_name},今年{_age}岁");
- }
- }
- public void ShowInfo(string name, int age)
- {
- WriteInfo info = new WriteInfo(name, age);
- Thread t = new Thread(info.ShowHumanInfo);
- t.Start();
- }
复制代码 常用属性
属性
| 描述
| CurrentThread
| 获取当前正在运行的线程
| IsAlive
| 获取当火线程的实验状态
| IsBackground
| 某个线程是否为配景线程
| IsThreadPoolThread
| 线程是否属于托管线程池
| ManagedThreadId
| 获取当前托管线程的唯一标识符
| Name
| 获取或设置线程的名称
| Priority
| 线程的优先级可以影响线程的调用顺序
| ThreadState
| 当火线程的状态
| 线程状态
Aborted
| 线程状态包罗 AbortRequested 并且该线程现在已死,但其状态尚未更改为 Stopped
| AbortRequested
| 已对线程调用了 Abort(Object) 方法,但线程尚未收到试图终止它的挂起的 ThreadAbortException
| Background
| 线程正作为配景线程实验。 此状态可以通过设置 IsBackground 属性来控制
| Running
| 线程已启动且尚未停止
| Stopped
| 线程已停止
| StopRequested
| 正在哀求线程停止
| Suspended
| 线程已挂起
| SuspendRequested
| 正在哀求线程挂起
| Unstarted
| 尚未对线程调用 Start() 方法
| WaitSleepJoin
| 线程已被阻止。 这可能是调用 Sleep(Int32) 或 Join()、哀求锁定或在线程同步对象上等待的结果
| 常用方法
方法
| 描述
| Start
| 开启线程
| Abort
| 终止线程
| Sleep
| 停息线程一段时间
| Join
| 阻塞调用线程,直到某个线程终止
| 例子:等待Thread线程完成后进行后续操作
- // 创建并启动新线程
- Thread newThread = new Thread(() =>
- {
- Console.WriteLine("子线程开始运行");
- // 模拟耗时操作
- Thread.Sleep(2000);
- Console.WriteLine("子线程结束运行");
- });
- newThread.Start();
- // 等待子线程结束
- newThread.Join();
- Console.WriteLine("主线程结束");
复制代码 ThreadPool 线程池
1、线程池创建的线程默认都是配景线程,不能把池中的线程修改为前台线程,也不能修改线程池中优先级与名称。
2、线程池中的线程只能用于时间比较短的任务,如果配景线程需要长时间运行,则需要单独开启,不适适用线程池。
3、手动创建多个Thread线程可能会消耗较多性能,通过线程池可以提高效率。
4、缺点
ThreadPool不支持线程的取消、完成、失败关照等操作;
ThreadPool不支持线程实验的先后序次;
- void ThreadPoolTest()
- {
- ThreadPool.QueueUserWorkItem(GenFile, "原神.apk");
- ThreadPool.QueueUserWorkItem(GenFile, "王者荣耀.apk");
- ThreadPool.QueueUserWorkItem(GenFile, "蛋仔排队.apk");
- ThreadPool.QueueUserWorkItem(GenFile, "炉石传说.apk");
- }
- void GenFile(object fileName)
- {
- Thread.Sleep(3000);
- Console.WriteLine($"生成了文件{fileName}.txt");
- }
复制代码 Task 任务
Task开启的线程是配景线程
开启线程
- //第一种方式
- Task task = new Task(() =>
- {
-
- });
- task.Start();
- //第二种方式 Task.Run
- Task task = Task.Run(() =>
- {
-
- });
- //第三种方式 TaskFactory
- TaskFactory taskFactory = new TaskFactory();
- Task task = taskFactory.StartNew(() =>
- {
-
- });
- //第四种方式 Task.Factory
- Task task = Task.Factory.StartNew(() =>
- {
-
- });
复制代码 常用方法
方法
| 描述
| Wait
| 等待Task完成实验
| WaitAny
| 等待列表中任一Task完成实验,同步方法,会阻塞主线程
| WaitAll
| 等待列表中全部Task完成实验,同步方法,会阻塞主线程
| WhenAny
| 创建一个任务,该任务将在任一提供的任务完成时完成。异步方法,不会阻塞主线程
| WhenAll
| 创建一个任务,该任务将在数组中的全部 Task 对象完成时完成。异步方法,不会阻塞主线程
| ContinueWith
| 在任务完成后回调一个延续任务,参数是调用方的任务信息
| - //并行运行多个任务,等待任务都运行完后添加延续事件逻辑
- List<Task> list = new List<Task>();
- list.Add(Task.Run(() =>
- {
- Console.WriteLine("开始做菜:");
- Thread.Sleep(3000);
- Console.WriteLine("做好素菜了!");
- }));
- list.Add(Task.Run(() =>
- {
- Console.WriteLine("开始做菜:");
- Thread.Sleep(5000);
- Console.WriteLine("做好荤菜了!");
- }));
- Task.WhenAll(list).ContinueWith(t =>
- {
- Console.WriteLine("菜都做好了,开饭吧!");
- });
复制代码 WaitAll和WhenAll的区别?
1、Task.WaitAll 是一种同步方法,它会阻塞调用线程,直到全部提供的任务都已完成。当需要确保一组任务在继续之前已完成时,该方法很有用,但它以阻塞方式实验,这意味着调用 Task.WaitAll 的线程会被占用,直到全部任务都完成为止。
2、Task.WhenAll 是一种异步方法,当全部提供的任务都完成后,该方法将返回单个任务。与 Task.WaitAll 差别,它不会阻止调用线程。相反,它答应调用代码继续异步实验。
async/await + Task
- async void TestAsync()
- {
- await Task.Run(() =>
- {
- Console.WriteLine("开始做菜:");
- Thread.Sleep(3000);
- Console.WriteLine("做好素菜了!");
- });
- Console.WriteLine("开饭了!");
- }
复制代码 多线程隐患
争用条件
程序的结果取决于两个或更多个线程中的哪一个先到达某一特定代码块时出现的一种 bug。 多次运行程序会产生差别的结果,并且无法推测任何给定运行的结果。
- public int gameState;
- void DeadLock()
- {
- Thread t1 = new Thread(ChangeMyState);
- Thread t2 = new Thread(ChangeMyState);
- t1.Start();
- t2.Start();
- }
- void ChangeMyState()
- {
- while (true)
- {
-
- gameState++;
- if (gameState == 100)
- {
- Console.WriteLine("啊哦,好像出现问题了");
- }
- gameState = 100;
- }
- }
复制代码 解决方案:用 lock 语句锁定在线程中共享的资源。
- public int gameState;
- public object objLock = new object();
- void DeadLock()
- {
- Thread t1 = new Thread(ChangeMyState);
- Thread t2 = new Thread(ChangeMyState);
- t1.Start();
- t2.Start();
- }
- void ChangeMyState()
- {
- lock (objLock)
- {
- while (true)
- {
- gameState++;
- if (gameState == 100)
- {
- Console.WriteLine("啊哦,好像出现问题了");
- }
- gameState = 100;
- }
- }
- }
复制代码
死锁
如果利用lock不当,就会产生死锁情况!
描述:两个线程中的每一个线程都尝试锁定另外一个线程已锁定的资源时,就会发生死锁,两个线程都不能继续实验。(公共资源被多个线程争抢,导致一个线程永久等待另一个线程开释资源的异常情况)
死锁的四个须要条件:
1.互斥条件:资源一次只能被一个线程占用。
2.持有并等待条件:线程持有一个资源并等待获取其他资源。
3.不可剥夺条件:线程已得到的资源条件不能被强行剥夺,只能由线程自己开释。
4.循环等待条件:存在一组线程,每个线程都在等待下一线程持有的资源,形成一个环形等待。
死锁问题复现
- object resourceA = new object();
- object resourceB = new object();
- int result;
- public void CheckDeadlock()
- {
- var thread1 = new Thread(Logic1);
- var thread2 = new Thread(Logic2);
- thread1.Start();
- thread2.Start();
- thread1.Join();
- thread2.Join();
- Console.WriteLine(result);
- }
- public void Logic1()
- {
- lock (resourceA)
- {
- Thread.Sleep(100);
- lock (resourceB)
- {
- result += 1;
- }
- }
- }
- public void Logic2()
- {
- lock (resourceB)
- {
- Thread.Sleep(100);
- lock (resourceA)
- {
- result += 2;
- }
- }
- }
复制代码 解决方案
方法一 调解锁的顺序
确保全部线程按雷同的顺序哀求锁。这可以打破死锁的循环等待条件。只要全部的线程都以雷同的顺序哀求资源,死锁就不会发生。
- public void Logic1()
- {
- lock (resourceA)
- {
- Thread.Sleep(100);
- lock (resourceB)
- {
- result += 1;
- }
- }
- }
- public void Logic2()
- {
- lock (resourceA)
- {
- Thread.Sleep(100);
- lock (resourceB)
- {
- result += 2;
- }
- }
- }
复制代码 方法二 锁的超时机制
利用Monitor.TryEnter 来设置获取锁的超时时间。如果超过指定时间无法获取锁,线程可以退出或实验其他操作。
- public void Thread1Work()
- {
- if (Monitor.TryEnter(resourceA, TimeSpan.FromSeconds(1))) // 尝试获取锁1,超时时间1秒
- {
- try
- {
- Console.WriteLine("Thread 1 acquired lock1");
- Thread.Sleep(100);
- if (Monitor.TryEnter(resourceB, TimeSpan.FromSeconds(1))) // 尝试获取锁2,超时时间1秒
- {
- try
- {
- result += 1;
- }
- finally
- {
- Monitor.Exit(resourceB); // 释放锁2
- }
- }
- else
- {
- Console.WriteLine("Thread 1 failed to acquire lock2, potential deadlock detected.");
- }
- }
- finally
- {
- Monitor.Exit(resourceA); // 释放锁1
- }
- }
- else
- {
- Console.WriteLine("Thread 1 failed to acquire lock1, potential deadlock detected.");
- }
- }
- public void Thread2Work()
- {
- if (Monitor.TryEnter(resourceB, TimeSpan.FromSeconds(1))) // 尝试获取锁2,超时时间1秒
- {
- try
- {
- Thread.Sleep(100);
- if (Monitor.TryEnter(resourceA, TimeSpan.FromSeconds(1))) // 尝试获取锁1,超时时间1秒
- {
- try
- {
- result += 2;
- }
- finally
- {
- Monitor.Exit(resourceA); // 释放锁1
- }
- }
- else
- {
- Console.WriteLine("Thread 2 failed to acquire lock1, potential deadlock detected.");
- }
- }
- finally
- {
- Monitor.Exit(resourceB); // 释放锁2
- }
- }
- else
- {
- Console.WriteLine("Thread 2 failed to acquire lock2, potential deadlock detected.");
- }
- }
复制代码 方法三 减少锁的持有时间
尽量缩小锁定的范围,确保只有在修改共享资源时才持有锁。这样可以减少锁竞争,低落死锁发生的机率。
参考链接
一文搞懂C#多线程、并发、异步、同步、并行 - biubiu12138 - 博客园
C# Task详解 - 五维思考 - 博客园
同步与异步:.NET 中的 Task.WaitAll 和 Task.WhenAll-CSDN博客
死锁(Deadlock)C#_c#死锁的缘故原由及解决方法-CSDN博客
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |