多线程基础知识(一)

基础知识

1) 一个应用程序就是一个进程,一个进程中至少有一个线程,线程可分为前台线程和后台线程。

2) 前台线程和后台线程

3) 一个人一边烧水一边洗衣服比“先烧水再洗衣服”效率高。同一时刻一个人只能干一件事情,其实是在“快速频繁切换”,如果处理不当可能比不用多线程效率还低。讨论多线程先只考虑“单核 cpu”。

4) 普通的代码是从上向下执行的,但是多线程的代码可以“并行”执行,可以把“线程”理解成独立的执行单元,线程中的代码可以“并行执行”。

  线程根据情况被分配给一定的“时间片”来运行,可能一个线程还没执行完,就又要把时间片交给别的线程执行。把要在单独的线程放到一个方法中,然后创建 Thread 对象,运行它,这个 Thread中的代码就会在单独的线程中执行。

5) 多线程的好处:

    有很大可能增加系统的运行效率;

    开发 winform 程序,避免界面卡;

    注册后向用户发送欢迎邮件,如果发送邮件很慢的话,避免注册过程很慢

 1 static void Main(string[] args)
 2         {
 3             
 4             int i = 5;
 5             //创建一个子线程
 6             Thread t1 = new Thread(() =>
 7             {
 8                 //返回到主线程中
 9                 Console.WriteLine("i="+i);
10             });
11             t1.Start();
12             Console.ReadLine();
13         }
Thread

参数化线程

 1 static void Main(string[] args)
 2         {
 3             
 4             int i = 5;
 5             //创建一个子线程
 6             Thread t1 = new Thread((obj) =>
 7             {//委托中添加参数
 8                 //返回到主线程中
 9                 Console.WriteLine("i=" + i);
10                 Console.WriteLine("obj="+ obj);
11             });
12             //参数化
13             t1.Start(i);
14             Console.ReadLine();
15         }

创建的十个线程执行的顺序是不一定的,所以出现这样的结果

线程睡眠

Thread.Sleep(n)指当前代码所在的线程“睡眠 N 毫秒”

线程退出

前后台线程的主要区别:

    进程中只要存在没有完成的前台线程,进程就不会被销毁。

    换句话说就是,如果所有的前台线程都完成了,进程就会被销毁,即使是存在未完成任务的后台线程。

    可以通过设置Thread.IsBackground来把线程设置为前台线程或者是后台线程。

    把线程设置为“后台线程”后,所有“非后台线程”执行结束后程序就会退出,不会等“后台线程”执行结束

线程优先级

 static void Main(string[] args)
        {
            Thread t1 = new Thread(()=> {
                Console.WriteLine("线程优先级");
            });
            //设置线程优先级,线程默认都是Normal级
            t1.Priority = ThreadPriority.Normal;
            t1.Start();
        }


public enum ThreadPriority
    {
        Lowest = 0,
        BelowNormal = 1,
        Normal = 2,
        AboveNormal = 3,
        Highest = 4
    }

线程的终止

  可以调用 Thread 的 Abort 方法终止线程的执行,会在当前执行的代码上“无风起浪”的抛出 ThreadAbortException,可以 catch 一下验证一下,一般不需要程序去 catch。

线程的同步

 1 class Program
 2     {
 3         private static int counter = 0;
 4         static void Main(string[] args)
 5         {
 6             Thread t1 = new Thread(() =>
 7             {
 8                 for (int i = 0; i < 1000; i++)
 9                 {
10                     counter++;
11                     Thread.Sleep(1);
12                 }
13             });
14             t1.Start();
15             Thread t2 = new Thread(() =>
16             {
17                 for (int i = 0; i < 1000; i++)
18                 {
19                     counter++;
20                     Thread.Sleep(1);
21                 }
22             });
23 
24             t2.Start();
25             while (t1.IsAlive) { };
26             while (t2.IsAlive) { };
27             Console.WriteLine(counter);
28             Console.ReadKey();
29         }
30 
31     }

当t1线程占用counter时t2线程可能已经改变了counter的值,造成数据的不同步

  解决方案:

①:lock关键字:对象互斥
②:[MethodImpl(MethodImplOptions.Synchronized)],只能有一个线程访问
③:Monitor类:lock关键字最终会被编译成Monitor

 1 class Program
 2     {
 3         private static int counter = 0;
 4         private static object locker = new object();
 5         static void Main(string[] args)
 6         {
 7             Thread t1 = new Thread(() =>
 8             {
 9                 for (int i = 0; i < 1000; i++)
10                 {
11                     lock (locker)
12                     {
13                         counter++;
14                     }
15                     Thread.Sleep(1);
16                 }
17             });
18             t1.Start();
19             Thread t2 = new Thread(() =>
20             {
21                 for (int i = 0; i < 1000; i++)
22                 {
23                     lock (locker) { 
24                         counter++;
25                     }
26                     Thread.Sleep(1);
27                 }
28             });
29 
30             t2.Start();
31             while (t1.IsAlive) { };
32             while (t2.IsAlive) { };
33             Console.WriteLine(counter);
34             Console.ReadKey();
35         }
36     }
lock解决

线程中其它的操作

1、Interrupt 用于提前唤醒一个在 Sleep 的线程,Sleep 方法会抛出 ThreadInterruptedException 异常

 1 Thread t1 = new Thread(()=> {
 2  Console.WriteLine("t1要睡了");
 3  try
 4  {
 5  Thread.Sleep(5000);
 6  }
 7  catch(ThreadInterruptedException)
 8  {
 9  Console.WriteLine("擦,叫醒我干啥");
10  }
11  Console.WriteLine("t1醒了");
12  });
13  t1.Start();
14  Thread.Sleep(1000);
15  t1.Interrupt();
View Code

2、Sleep 是静态方法,只能是自己主动要求睡,别人不能命令他睡

3、已经过时的方法:Suspend、Resume。不要用

4、Abort()方法会强行终止线程,会引发线程内当前在执行的代码发出 ThreadAbortException异常

5、t1.Join()当前线程等待 t1 线程执行结束(Join 这里翻译成“连接”:你完了我再接着)

 1 class Program
 2     {
 3         
 4         static void Main(string[] args)
 5         {
 6             Thread t1 = new Thread(() =>
 7             {
 8                 for (int i = 0; i < 100; i++)
 9                 {
10                     Console.WriteLine("t1 " + i);
11                 }
12             });
13             t1.Start();
14             Thread t2 = new Thread(() =>
15             {
16                 t1.Join();//等着 t1 执行结束
17                 for (int i = 0; i < 100; i++)
18                 {
19                     Console.WriteLine("t2 " + i);
20                 }
21             });
22             t2.Start();
23         }
24     }

单例模式与多线程

简单实用的,可在单线程也可在多线程中使用:

1 class God{
2     private static God instance=new God();
3     private God(){
4     
5     }
6     public static God GetInstance(){
7         return instance;
8     }
9 }

懒汉模式:他的问题在于如果是多线程则可能有2个线程同时访问if(instance==null),则会出现问题

 1 class God{
 2     private static God instance=null;
 3     private God(){}
 4     
 5     public static God GetInstance(){
 6         if(instance==null){
 7             instance=new God();
 8         }
 9         return instance;
10     }
11 }

多线程下的懒汉模式:这种情况在每次创建时都访问lock是会造成性能下降

 1 class God{
 2     private static God instance=null;
 3     private God(){}
 4     private static object locker=new object();
 5     
 6     public static God GetInstance(){
 7         lock(locker){
 8             if(instance==null){
 9                 instance=new God();
10             }
11             return instance;
12         }
13     }
14 }

多线程下双重锁单利:

 1 class God{
 2     private static God instance=null;
 3     private God(){}
 4     private static object locker=new object();
 5     
 6     public static God GetInstance(){
 7         if(instance==null){
 8             lock(locker){
 9                 if(instance==null){
10                     instance=new God();
11                 }
12                 return instance;
13             }
14         }
15     }
16 }

WaitHandle

  除了锁之外,.Net 中还提供了一些线程间更自由通讯的工具,他们提供了通过“信号”进行通讯的机制,通俗的比喻为“开门”、“关门”:Set()开门,Reset()关门,WaitOne()等着开门

WaitHandle抽象类

ManualResetEvent:手动开关

  WaitOne():开一次门

 1 ManualResetEvent mre=new ManualResetEvent(false);//默认值给的是否开门,false关门
 2 Thread t1=new Thread(()=>{
 3     cw("开始等待着开门");
 4     mre.WaitOne();//开一次门
 5     cw.("终于等到你开门");
 6 });
 7 t1.start();
 8 cw("按任意键开门");
 9 cr();
10 mre.Set();
11 cr();
12 
13 
14 //WaitOne()还可以设置等待时间
15 //mre.Reset()手动关门
View Code

  WaitAll():用来等待所有信号都变为“开门状态”

  WaitAny():用来等待任意一个信号都变为“开门状态”。
AutoResetEvent他是在开门并且一个 WaitOne 通过后自动关门,因此命名为“AutoResetEvent”(Auto 自动-Reset 关门)

 1 AutoResetEvent are = new AutoResetEvent(false);
 2 Thread t1 = new Thread(() => {
 3 while (true)
 4 {
 5 Console.WriteLine("开始等着开门");
 6 are.WaitOne();
 7 Console.WriteLine("终于等到你");
 8 }
 9 });
10 t1.Start();
11 Console.WriteLine("按任意键开门");
12 Console.ReadKey();
13 are.Set();//开门
14 Console.WriteLine("按任意键开门");
15 Console.ReadKey();
16 are.Set();
17 Console.WriteLine("按任意键开门");
18 Console.ReadKey();
19 are.Set();
View Code

ManualResetEvent 就是学校的大门,开门大家都可以进,除非主动关门;

AutoResetEvent:就是火车地铁的闸机口,过了一个后自动关门

原文地址:https://www.cnblogs.com/cuijl/p/7652692.html