您的位置:首页 > 其它

多线程的总结

2020-04-02 08:03 344 查看

最近学习了多线程,发现还挺好玩的,就是有点绕,经常把自己绕晕,要敲几遍代码才能缓过来(笑)
现在做记录吧

多线程要么提速,要么提升体验

委托的多线程

委托中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
  • 收藏
  • 分享
  • 文章举报
漫漫长路,慢慢走 发布了12 篇原创文章 · 获赞 2 · 访问量 234 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: