多线程的总结
最近学习了多线程,发现还挺好玩的,就是有点绕,经常把自己绕晕,要敲几遍代码才能缓过来(笑)
现在做记录吧
多线程要么提速,要么提升体验
委托的多线程
委托中Invoke就是顺序执行,而beginInvoke进行异步
然后参数中第一个,是执行完成后把自身作为参数传递到一个AsyncCallback中,然后执行这个AsyncCallback,最后的参数是一个标识符,同时每个异步只能调用一次
private void 线程开始(string cs) { Console.WriteLine("线程启动,这是进行{0}测试,这是第{1}核,启动时间是{2}",cs, Thread.CurrentThread.ManagedThreadId.ToString("00"), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); long d = 0; for (int i = 0; i < 1000000000; i++) { d+=i; } Console.WriteLine($"线程结束,这是进行{cs}测试,这是第{Thread.CurrentThread.ManagedThreadId.ToString("00")}核,结束时间是{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } private void button1_Click(object sender, EventArgs e) { Action<string> action = this.线程开始; action.Invoke("同步"); } private void button2_Click(object sender, EventArgs e) { //相当于先执行异步,然后把结果作为值传递到fanhuizhi中并执行 AsyncCallback fanhuizhi = yibu => //这个yibu等于action.BeginInvoke("异步", fanhuizhi, "状态标识符"); { Console.WriteLine(yibu.AsyncState);//这里返回状态标识符 Console.WriteLine("异步结束"); }; Action<string> action = this.线程开始; action.BeginInvoke("异步", fanhuizhi, "状态标识符"); }
IsCompleted是进行是否完成线程的判断
然后这个放在button2里面,这是一个延迟的判断,第一个是会依次打印,最后正常显示上传成功,然后第二个则会多打印一个上传中,
这个原因在于,第一个是先执行判断,判断完成后,打印之后,再将线程延迟200毫秒
而第二个,则是先判断,此时线程停止200毫秒,在进行判断,最后会导致在线程结束后面出现一次循环,因为在判断后,停顿200毫秒,然后再判断,注意,while循环不会走多线程,只能一次判断一次,我就是这个搞混了,绕晕了,看啥都像多线程(笑),想了好久,忘了这个不循环了
int i = 0; //while (!yibu.IsCompleted) //{ // if (i < 10) // { // Console.WriteLine($"文件上传完成{i++ * 10}%"); // } // else // { // Console.WriteLine($"文件上传完成99.9%"); // } // Thread.Sleep(200); //} i = 0; while (!yibu.IsCompleted) { Thread.Sleep(200); if (i < 10) { Console.WriteLine($"文件上传完成{i++ * 10}%"); } else { Console.WriteLine($"文件上传完成99.9%.."); } } Console.WriteLine($"上传成功");
AsyncWaitHandle.WaitOne是一个等待,必须要等这行代码之前的线程完成后,才执行后面的,而如果有参数,则是最多等待多少秒,比如线程跑10秒,这里面写个500毫秒,就是只等线程跑500毫秒就不等了
Thread.Sleep(200); Console.WriteLine("等待"); yibu.AsyncWaitHandle.WaitOne(); Console.WriteLine("等待2");
EndInvoke和beginInvoke差不多,只不过会返回值,并且能更好的重用线程
ManualResetEvent 则是线程阻塞,要手动关闭打开
ManualResetEvent zusenxianchen = new ManualResetEvent(false); //设置线程阻塞默认为关闭 ThreadPool.QueueUserWorkItem(fangfa => { zusenxianchen.Reset();//这个是重置为关闭,就是把下面的WaitOne关闭了 Console.WriteLine($"这个是启动线程{Thread.CurrentThread.ManagedThreadId}"); zusenxianchen.Set();//这个是允许线程完成,就是把下面的WaitOne开通了 }); zusenxianchen.WaitOne(); Console.WriteLine("这是执行完了"); //但是一般不要阻塞,因为万一多线程,然后多个阻塞,最后会卡死,比如如下操作 { ThreadPool.SetMaxThreads(2, 2);//设置线程上限2个 zusenxianchen.Reset(); for (int i = 0; i < 6; i++) { if (i < 3) { zusenxianchen.WaitOne(); } else { zusenxianchen.Set(); } } if (zusenxianchen.WaitOne()) { Console.WriteLine("没有阻塞线程"); } }
读取和设置线程上限
GetMaxThreads还有GetMinThreads是读取最大最小线程数 SetMaxThreads还有SetMinThreads则是读取最大最小线程数 int a=0, b=0; ThreadPool.GetMaxThreads(out a, out b); Console.WriteLine($"{a},{b}"); ThreadPool.GetMinThreads(out a, out b); Console.WriteLine($"{a},{b}");
这些是Task
task比委托方便,可以直接task.run(()=>方法名),在括号里面跑要跑的方法
Task.Run(() => this.线程开始("TASK.RUN异步"));
并且还可以用List tl = new List();来存放多个方法,然后用
TaskFactory tf = Task.Factory;
tf.StartNew(() => 方法名);
来把方法放进去,然后用多线程跑
以下是执行方法
Task.WaitAll(tl.ToArray());//等所有线程结束,才完成
Task.WaitAny(tl.ToArray());//WaitAny任意一个完成
Task.WhenAll(tl.ToArray()).ContinueWith(t =>方法名));
WhenAll比WaitAll好的是不会卡顿,然后ContinueWith里面还可以放一个回调方法,执行完了后自动执行
WhenAny比WaitAny好的是不会卡顿,然后ContinueWith里面还可以放一个回调方法,执行完了后自动执行
TaskFactory tf = Task.Factory; List<Task> tl = new List<Task>(); { tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步1"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步2"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步3"))); Task.WaitAll(tl.ToArray());//WaitAll是等所有完成 Console.WriteLine("全部完成,提示这句话"); } { tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步1"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步2"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步3"))); Task.WaitAny(tl.ToArray());//WaitAny任意一个完成 Console.WriteLine("有一个完成了,提示这句话"); } { tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步1"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步2"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步3"))); Task.WhenAll(tl.ToArray()).ContinueWith(t => Console.WriteLine($"全部执行后再执行的,是第{Thread.CurrentThread.ManagedThreadId.ToString()}线程")); //WhenAll比WaitAll好的是不会卡顿,然后ContinueWith相当于后面的Console.WriteLine,执行完了后自动执行 } { tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步1"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步2"))); tl.Add(tf.StartNew(() => this.线程开始("TaskFactory异步3"))); Task.WhenAny(tl.ToArray()).ContinueWith(t => Console.WriteLine($"某一个线程执行完后执行的,是第{Thread.CurrentThread.ManagedThreadId.ToString()}线程")); //WhenAny比WaitAny好的是不会卡顿,然后ContinueWith相当于后面的Console.WriteLine,执行完了后自动执行 }
然后可以灵活运用,线程里面跑线程,来完成很多效果,比如下面,线程里面跑线程来设置一次跑多少线程,并且全部跑完才返回值
List<Task> lt = new List<Task>(); //做10个任务,但是一次只能跑6个线程 for (int i = 1; i <= 10; i++) { int k = i; lt.Add(Task.Run(() => this.线程开始(k.ToString()))); if (lt.Count >= 6)//就是存了6个就开始跑,不够就加 Task.WaitAny(lt.ToArray()); } Task.WhenAll(lt.ToArray()).ContinueWith(t => Console.WriteLine("跑完了")); TaskFactory tf = Task.Factory; List<Task> lt = new List<Task>(); //做10个任务,但是一次只能跑6个线程 for (int i = 1; i <= 10; i++) { int k = i; lt.Add(Task.Run(() => this.线程开始(k.ToString()))); if (lt.Count >= 6)//就是存了6个就开始跑,不够就加 Task.WaitAny(lt.ToArray()); } tf.ContinueWhenAll(lt.ToArray(), t => Console.WriteLine("跑完了")); Task.WhenAll(lt.ToArray()).ContinueWith(t => Console.WriteLine("跑完了2"));
以下是并行编程,这些相当于把TASK封装了,但是不怎么常用
Parallel.Invoke(() => this.线程开始("1"), () => this.线程开始("2"), () => this.线程开始("3")); //同时跑3个线程 Parallel.For(0, 3, i => this.线程开始(i.ToString())); //跑3个相同的 Parallel.ForEach(new string[] { "1", "2", "3" }, i => this.线程开始(i)); //跑数组 ParallelOptions p = new ParallelOptions(); p.MaxDegreeOfParallelism = 2;//设置最大运行数量 Parallel.For(0, 3,p, i => this.线程开始(i.ToString())); { Task.Run(() => { p.MaxDegreeOfParallelism = 2;//设置最大运行数量 Parallel.For(0, 3, p, i => this.线程开始(i.ToString())); }); }
取消并且返回值
CancellationTokenSource ct = new CancellationTokenSource();//取消 TaskFactory tf = Task.Factory; List<Task> lt = new List<Task>(); for (int i = 1; i <= 10; i++) { int k = i; lt.Add(tf.StartNew(t => { try { if (t.ToString() == "5") throw new Exception("这是第5个"); else if (t.ToString() == "3") throw new Exception("这是第3个"); else Console.WriteLine($"{t}"); } catch (Exception ex) { ct.Cancel(); Console.WriteLine(ex.Message); }},k, ct.Token));//ct会获取报的错误值,并且返回 } tf.ContinueWhenAll(lt.ToArray(), t => Console.WriteLine("跑完了")); Task.WhenAll(lt.ToArray()).ContinueWith(t => Console.WriteLine("跑完了2"));
线程安全
线程内部东西是共享,没问题,但是外面的东西放进去,会出问题,比如进行1到100加法,外面一个i=0,放进去,有时候会有多个线程同时想加,然后导致最后的值小于真实值,所以可以放在线程里面,最后在返回,或者进行lock加锁,一次进行一次运算,但是lock只能锁引用类型,相当于锁内存里面的,所以不能锁string,因为相同值的是一个内存,最后会让多个锁锁一个,出bug
因此,最好锁private隐私类型,static唯一,readoly不要改,object引用类型
也不能
但是使用了lock后,就没了并发了,所以最好在影响最小的地方锁
最好的方式应该就是数据拆分
线程的优先级别
当线程之间争夺CPU时间时,CPU按照线程的优先级给予服务。高优先级的线程可以完全阻止低优先级的线程执行。.NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。但是实际上线程是由系统分配,无非控制的,只能控制开头
成员名称 | 说明 |
---|---|
Lowest | 可以将 Thread 安排在具有任何其他优先级的线程之后。 |
BelowNormal | 可以将 Thread 安排在具有Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。 |
Normal | 默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有BelowNormal 优先级的线程之前。 |
AboveNormal | 可以将 Thread 安排在具有Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。 |
Highest | 可以将 Thread 安排在具有任何其他优先级的线程之前。 |
然后以下是属性
线程的常用属性
属性名称 | 说明 |
---|---|
CurrentContext | 获取线程正在其中执行的当前上下文。 |
CurrentThread | 获取当前正在运行的线程。 |
ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState | 获取一个值,该值包含当前线程的状态。 |
通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。
一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。
CurrentThread是最常用的一个属性,它是用于获取当前运行的线程
ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过
然后一起使用Thread.CurrentThread.ManagedThreadId获取当前线程名。
System.Threading.Thread的方法
Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁。
方法名称 | 说明 |
---|---|
Abort() | 终止本线程。 |
GetDomain() | 返回当前线程正在其中运行的当前域。 |
GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
Join() | 已重载。 阻塞调用线程,直到某个线程终止时为止。 |
Resume() | 继续运行已挂起的线程。 |
Start() | 执行本线程。 |
Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Sleep() | 把正在运行的线程挂起一段时间。 |
- 点赞 1
- 收藏
- 分享
- 文章举报
- 单例模式与多线程总结(完整版)
- Java 多线程和线程同步总结
- java多线程 --总结2
- 多线程学习总结----线程创建
- java多线程学习总结
- Java总结篇系列:Java多线程(一)
- 第01天多线程网络:(15):同步和异步函数的区别和总结
- python--threading多线程总结
- 总结一下碰到的多线程的问题
- Java 多线程面试题总结
- 40个Java多线程问题总结
- <转>Android多线程总结
- 多线程生产者与消费者问题的总结
- Java多线程面试总结
- Java 多线程的总结
- Java 多线程总结
- Java概念总结(一)【面向对象--多线程】
- 面试总结-进程、线程与多线程
- 我的专题文章之三------对多核多线程的总结
- 40个Java多线程问题总结