zoukankan      html  css  js  c++  java
  • 线程池和Thread

    1、线程池
    创建线程需要时间。如果有不同的短任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多线程时增加,在需要释放资源时减少。不需要自己创建这样一个列表。该列表有ThreadPool类托管。这个类会在需要时增减池中线程的线程数,直到最大的线程数。池中的最大线程数是可配置的。在四核CPU中,默认设置为1023个工作线程和1000个I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
    下面的示例应用程序首先要读取工作线程和I/O线程的最大线程数,把这些信息写入控制台中,接着在for循环中,调用ThreadPool.QueueUserWorkItem()方法,传递一个WaitCallback类型的委托,来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池已经在运行,且有一个空闲线程来完成该任务,就把该任务传递给这个线程。

           int nWorkerThreads;
            int nCompletionPortThreads;
            ThreadPool.GetMaxThreads(out nWorkerThreads,out nCompletionPortThreads);
            Console.WriteLine("Max worker threads:{0}  ,I/O completion threads:{1}",nWorkerThreads,nCompletionPortThreads);
    
            for (int i = 0; i < 5; i++)
            {
                ThreadPool.QueueUserWorkItem(JobForAThread);
            }
    
       static void JobForAThread(object state)
        {
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine("loop {0}, running inside pooled thread {1}",i,Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50);
            }
        }
    


    运行应用程序时,可以看到1023个工作线程的当前设置。5个任务只由4个线程池中的线程处理(这是一个四核系统)。

    线程池的一些限制:
    a、线程池中的线程都是后台线程。如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线程。
    b、不能给入池的线程设置优先级或名称。
    c、对于COM对象,入池的所有线程都是多线程单元(MTA)线程许多COM对象都需要单线程单元(STA)线程。
    d、入池的线程只能用于时间较短的任务。如果线程要一直运行就应使用Thread类创建一个线程(或者在创建Task时使用LongRunning选项)。

    2、Thread类
    使用Thread类可以创建和控制线程。下面的结合Lambda表达式示例,结果不能保证哪一个先输出。

           var t1 = new Thread(() => Console.WriteLine("running in a thread,id:{0}",Thread.CurrentThread.ManagedThreadId));
            t1.Start();
            Console.WriteLine("This is the main ,id:{0}",Thread.CurrentThread.ManagedThreadId);
    


    2.1、给线程传递参数
    给线程传递一些数据可以采用两种方式。一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另一种是创建一个自定义类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,之后启动线程。
    第一种:

           var d = new Data { message="this is a thread"};
            var t1 = new Thread(ThreadMainWithParameters);
            t1.Start();
        static void ThreadMainWithParameters(object o)
        {
            Data d = (Data)o;
            Console.WriteLine("Running in a thread,receiver {0}");
        }
        public class Data
        {
            public string message;
        }
    

    第二种:

        public class MyThread
        {
            private string data;
    
            public MyThread(string data)
            {
                this.data = data;
            }
            public void ThreadMain()
            {
                Console.WriteLine("Running in a thread,data:{0}",data);
            }
           }
        var obj = new MyThread("info");
            var t3 = new Thread(obj.ThreadMain);
            t3.Start();
    

    2.2、后台线程
    只要有一个前台线程在运行,应用程序的进程就在运行。如果多个前台线程在运行,而Main()方法结束了,应用程序的进程就仍然是激活的,知道所有前台线程完成其任务为止。在默认情况下,用Tread类创建的线程是前台线程。线程池中的线程总是后台线程。在用Thread类创建线程时,可以设置IsBackground属性,以确定该线程是前台线程还是后台线程。
    2.3、线程的优先级
    前面提到,线程由操作系统调度。给线程指定优先级,就可以影响调度顺序。在改变优先级之前,必须理解线程调度器。操作系统根据优先级来调度线程。调度优先级最高的线程以在CPU上运行。线程如果在等待资源,它就会停止运行,并释放CPU。
    线程必须等待时有几个原因,例如,响应睡眠指令、等待磁盘I/O的完成,等待网络包的到达等。如果线程不是主动释放CPU,线程调度器就会抢占该线程。线程有一个时间量,这意味着它可以持续使用CPU,知道这个时间到达(这是指没有更高优先级的线程时)。如果优先级相同的多个线程等待使用CPU,线程调度器就会使用一个循环调度规则,将CPU逐个交给线程使用。如果线程被其他线程抢占,它就会排在队列的最后。
    只有优先级相同的多个线程在运行,才用的上时间量和循环规则。优先级是动态的。如果线程是CPU密集型的(一直需要CPU,且不等待资源),其优先级就降低为该线程定义的基本优先级。如果线程在等待资源,它的优先级会提高。由于优先级的提高,线程很有可能在下次等待结束时获得CPU。
    在Thread类中,可以设置Priority属性,以影响线程的基本优先级。Priority属性需要ThreadPriority枚举定义的一个值。定义的级别有Highest、AboveNormal、Normal、BelowNormal和Lowest。(在给线程指定较高级的优先级的时候要小心,因为这可能降低其他线程的运行概率,根据需要、可以短暂地改变优先级)
    2.4、控制线程
    调用Thread对象的Start()方法,可以创建线程。但是,在调用Start()方法后,新线程仍不是出于Running状态,而是处于Unstarted状态。只要操作系统的线程调度器选择了要运行的线程,线程就会改变Running状态,读取Thread.ThreadState属性,就可以获得线程的当前状态。
    使用Thread.Sleep()方法,会使线程处于WaitSleepJoin状态,在等待Sleep()方法定义的时间段后,线程就会再次被唤起。
    要停止另一个线程,可以调用Thread.Abort()方法调用这个方法时,会在接到终止命令的线程中抛出一个ThreadAbortException类型的异常。用一个处理程序捕获这个异常,线程可以在结束前就完成一些清理工作。如果调用了Thread.ResetAbort,线程还有机会接收到ThreadAbortException异常后继续运行,如果线程,没有重置终止,接收到终止请求的线程的状态就从AbortRequest改为Aborted。
    如果需要等待线程的结束,就可以调用Thread.join()方法。Thread.join()方法会停止当前线程,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。
    3、线程问题
    使用多个线程编程并不容易。在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务,并行LINQ或Parallel类,也会遇到这些问题。为了避免这些问题,必须特别注意同步问题和多个线程可能发生的其他问题。
    3.1、争用条件
    如果两个或多个线程访问相同的对象,并且对共享状态的访问没有同步,就会出现争用条件。为了说明争用条件下面定义一个StateObject类,它包含一个int字段和一个ChangeState()方法。在ChangeState()方法的实现代码中,验证状态是否包含5,如果包含,就递增其值。下一条语句是Trace.Assert,它立刻验证State现在是否包含6.。

      public class StateObject
        {
            private int state = 5;
    
            public void ChangeState(int loop)
            {
                if(state==5)
                {
                    state++;
                    Trace.Assert(state==6,"Race condition occurred after"+loop+"loops");
                }
                state = 5;
            }
        }
    

    下面通过给任务顶一个方法来验证这一点

      public class SampleTask
        {
            public  void RaceCondition(object o)
            {
                Trace.Assert(o is StateObject,"o must be of type StateObject");
                StateObject state = o as StateObject;
    
                int i = 0;
                while(true)
                {
                    state.ChangeState(i++);
                }
            }
        }
    

    在Main()方法中新建StateObject对象,它由所有任务共享。通过使用传递给Task的Run方法的lambda表达式调用RaceCondition方法来创建Task对象。然后,主线程等待用户输入。但是,可能出现争用,所以程序很有可能在读取用户输入前就挂了。

          var state = new StateObject();
            for (int i = 0; i < 2; i++)
            {
                Task.Run(()=>new SampleTask().RaceCondition(state));
            }
    


    要避免该问题,可以锁定共享的对象。这可以在线程中完成,用下面的Lock语句锁定在线程中共享的state变量。只有一个线程能在锁定的块中处理共享的state对象。由于这个对象在所有的线程之间共享,因此如果一个线程锁定了state。另一个线程就必须等待该锁定的解除。一旦接受锁定,线程就拥有该锁定,直到该锁定块的末尾才能解除锁定,如果改变state变量引用的对象的每个线程都使用一个锁定,就不会出现争用条件。

     public class SampleTask
        {
            public  void RaceCondition(object o)
            {
                Trace.Assert(o is StateObject,"o must be of type StateObject");
                StateObject state = o as StateObject;
    
                int i = 0;
                while(true)
                {
                    lock(state)//no race condition with this lock
                    {
                        state.ChangeState(i++);
                    }
                   
                }
            }
        }
    

    在使用共享对象时,除了进行锁定之外,还可以将共享对象设置为线程安全的对象。在下面的代码中,ChangeState方法中包含一条lock语句,由于不能锁定state本身(只有引用类型才能用于锁定)

     public class StateObject
        {
            private int state = 5;
            private object _lock = new object();
            public void ChangeState(int loop)
            {
                lock(_lock)
                {
                    if (state == 5)
                    {
                        state++;
                        Trace.Assert(state == 6, "Race condition occurred after" + loop + "loops");
                    }
                    state = 5;
                }                
            }
        }
    

    3.2、死锁
    过多的锁定也会有麻烦。在死锁中,至少有两个线程被挂起,并等待对方解除锁定。由于两个线程都在等待对方,就出现了死锁,线程将无限等待下去。为了说明死锁,下面实例化StateObject类型的两个对象,并把它们传递给SampleTask类的构造函数,创建两个任务,其中一个任务运行Deadlock1()方法,另一个任务运行Deadlock2()方法。

           var state1 = new StateObject();
            var state2 = new StateObject();
            new Task(new Sample(state1,state2).DeadLock1).Start();
            new Task(new Sample(state1, state2).DeadLock2).Start();
      public class Sample
        {
            public Sample(StateObject s1, StateObject s2)
            {
                this.s1 = s1;
                this.s2 = s2;
            }
            private StateObject s1;
            private StateObject s2;
    
            public void DeadLock1()
            {
                int i = 0;
                while(true)
                {
                    lock(s1)
                    {
                        lock(s2)
                        {
                            s1.ChangeState(i);
                            s2.ChangeState(i++);
                            Console.WriteLine("still running , {0}",i);
                        }
                    }
                }
            }
    
            public void DeadLock2()
            {
                int i = 0;
                while (true)
                {
                    lock (s2)
                    {
                        lock (s1)
                        {
                            s1.ChangeState(i);
                            s2.ChangeState(i++);
                            Console.WriteLine("still running , {0}", i);
                        }
                    }
                }
            }
        }
    

    在本例中只需要改变锁定顺序,这两个线程就会以相同的顺序进行锁定。但是,锁定可能隐藏在方法的深处。为了避免这个问题,可以在应用程序的体系架构中,从一开始就设计好锁定顺序,也可以为锁定定义超时时间。

  • 相关阅读:
    面向接口程序设计思想实践
    Block Chain Learning Notes
    ECMAScript 6.0
    Etcd Learning Notes
    Travis CI Build Continuous Integration
    Markdown Learning Notes
    SPRING MICROSERVICES IN ACTION
    Java Interview Questions Summary
    Node.js Learning Notes
    Apache Thrift Learning Notes
  • 原文地址:https://www.cnblogs.com/caozhengze/p/10067352.html
Copyright © 2011-2022 走看看