zoukankan      html  css  js  c++  java
  • C# 线程与进程

    进程与线程


    进程

    当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源,如Window句柄,文件系统句柄或其他内核对象。每个进程都分配的虚拟内存。

    而一个进程又是由多个线程所组成的。

    可以打开计算机设备管理查看自己电脑CPU数目,Ctrl+Alt+.调出任务管理器也可以查看,任务管理器还有详细的目前进程数目和线程数目。

    进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

    线程

    线程是程序中独立的指令流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),一个进程的内存空间是共享的,每个线程都可以使用这些共享空间。即不同的线程可以执行同样的函数。

    任何程序在执行时,至少有一个主线程。

    多线程


    多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    一个进程的多个线程可以同时运行在不同的CPU上或多核CPU的不同内核上。

    多线程的好处:

    可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

    多线程的不利方面:

    1. 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
    2. 多线程需要协调和管理,所以需要CPU时间跟踪线程
    3. 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
    4. 线程太多会导致控制太复杂,最终可能造成很多Bug

    操作系统的设计

    归结为三点:

    1. 以多进程形式,允许多个任务同时运行
    2. 以多线程形式,允许单个任务分成不同的部分运行
    3. 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

    利用异步委托去创建线程


    创建线程的一种简单方式是定义一个委托,并异步调用它。

    委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。接下来定义一个方法,使用委托异步调用(开启一个线程去执行这个方法)。

    当我们通过BeginInvoke开启一个异步委托的时候返回的结果是IAsyncResult,我们可以通过它的AsyncWaitHandle属性访问等待句柄。

     1   static string TakesAWhile(int times)
     2   {
     3       Console.WriteLine("异步函数开始!");
     4       Thread.Sleep(times);//程序运行到这里的时候会暂停ms毫秒
     5       return "异步结束!";
     6   }
     7   public delegate string TakesAWhileDelegate(int ms);// 声明委托
     8   static void Main(string[] args)
     9   {
    10       TakesAWhileDelegate _delegateTasks = TakesAWhile;
    11       IAsyncResult ar = _delegateTasks.BeginInvoke(3000, null, null);
    12       while (ar.IsCompleted == false)
    13       {
    14         Console.Write("-");
    15          Thread.Sleep(10);//每隔10mswhile循环一次,Thread类控制线程
    16       }
    17       Console.WriteLine(_delegateTasks.EndInvoke(ar));
    18   }

    这个属性返回一个WaitHandler类型的对象,它中的WaitOne()方法可以等待委托线程完成其任务,WaitOne方法可以设置一个超时时间作为参数(要等待的最长时间),如果发生超时就返回false。

     1  static string TakesAWhile(int times)
     2  {
     3    Console.WriteLine("异步函数开始!");
     4    Thread.Sleep(times);//程序运行到这里的时候会暂停ms毫秒
     5    return "异步结束!";
     6  }
     7  public delegate string TakesAWhileDelegate(int ms);// 声明委托
     8  static void Main(string[] args)
     9  {
    10 
    11    TakesAWhileDelegate _delegateTasks = TakesAWhile;
    12    IAsyncResult ar = _delegateTasks.BeginInvoke(3000, null, null);
    13    while (true)
    14    {
    15      Console.Write("-");
    16      if (ar.AsyncWaitHandle.WaitOne(10, false))// 等待每隔10ms,如果当前异步操作执行完毕时就会返回一个true
    17      {
    18        Console.WriteLine("异步结束!");
    19        break;
    20      }
    21    }
    22    Console.WriteLine(_delegateTasks.EndInvoke(ar));
    23  }

     等待委托的结果的第3种方式是使用异步回调。在BeginInvoke的第三个参数中,可以传递一个满足AsyncCallback委托的方法,AsyncCallback委托定义了一个IAsyncResult类型的参数其返回类型是void。

    对于最后一个参数,可以传递任意对象,以便从回调方法中访问它。(我们可以设置为委托实例,这样就可以在回调方法中获取委托方法的结果)

    格式如下:

    委托对象.BeginInvoke(委托的参数列表, 回调函数,对象);

    例子如下:

     1  static string TakesAWhile(int times)
     2  {
     3    Console.WriteLine("异步函数开始!");
     4             Thread.Sleep(times);//程序运行到这里的时候会暂停ms毫秒
     5             return "异步结束!";
     6         }
     7         public delegate string TakesAWhileDelegate(int ms);// 声明委托
     8  static void Main(string[] args)
     9  {
    10 
    11    TakesAWhileDelegate _delegateTasks = TakesAWhile;
    12 
    13    _delegateTasks.BeginInvoke(3000, TakesAWhileCompleted,_delegateTasks);
    14    while (true)
    15    {
    16      Console.Write("-");
    17      Thread.Sleep(10);
    18    }
    19      
    20  }
    21  static void TakesAWhileCompleted(IAsyncResult ar)
    22  {//回调方法是从委托线程中调用的,并不是从主线程调用的,可以认为是委托线程最后要执行的程序
    23    if (ar == null) throw new ArgumentNullException("ar");
    24    TakesAWhileDelegate _temp = ar.AsyncState as TakesAWhileDelegate;
    25    Console.Write(_temp.EndInvoke(ar));
    26  }

    可以使用在BeginInvoke中使用Lambda表达式,更方便:

    1     _delegateTasks.BeginInvoke(3000, ar => Console.WriteLine(_delegateTasks.EndInvoke(ar)), null);
    2     while (true)
    3     {
    4         Console.Write("-");
    5         Thread.Sleep(10);
    6     }

    开启BeginInvoke后,判断线程是否结束的方法,总结如下

    • 利用IAsyncResult中的IsCompleted属性判断是否完成
    • 获取IAsyncResult中的AsyncWaitHandle.WaitOne()线程超时是否,超时的返回参数true
    • 利用BeginInvoke的第三个参数AsyncCallback委托的方法

    利用Thread类去创建和控制线程


    MSDN查阅地址:

    https://msdn.microsoft.com/zh-cn/library/system.threading.thread(v=VS.95).aspx

    使用Thread类可以创建和控制线程,并获取其状态。Thread构造函数的参数是一个的委托类型。

    例如:

     1     static void Main(string[] args)
     2     {
     3         Thread _nextThread = new Thread(ThreadNext);
     4         _nextThread.Start();//线程开启
     5         Console.WriteLine("主线程运行!");
     6         Thread.Sleep(50);
     7         _nextThread.Abort();//终止线程
     8         Console.WriteLine("线程终止!");
     9     }
    10     static void ThreadNext() {
    11         while (true)
    12         {
    13             Console.WriteLine("线程开启—");
    14         }
    15     }

     若要向Thread类传值,有两种方法,第一种:使用带ParameterizedThreadStart委托参数的Thread构造函数

    官方描述的Thread类两个构造函数

    需注意:线程不会在创建时开始执行。 若要为执行而调度线程,请调用 Start 方法。 若要将数据对象传递给线程,请使用 Start(Object) 方法重载。

    特别注意:传递的值为Object对象!

    使用例子:

     1         static void Main(string[] args)
     2         {
     3             string str ="线程进行--";
     4             Thread _nextThread = new Thread(ThreadNext);
     5             _nextThread.Start(str);//传入一个对象
     6             Console.WriteLine("主线程运行!");
     7             Thread.Sleep(20);
     8             _nextThread.Abort();//终止线程
     9             Console.WriteLine("线程终止!");
    10         }
    11         static void ThreadNext(object str) {//这里参数类型必须为object对象!
    12             while (true)
    13             {
    14                 Console.WriteLine(str);
    15             }
    16         }

     还有一种方法:初始化一个对象,对象内部的方法去调用对象里面的成员,线程方法(实例方法)就可以调用内部成员达到传值的目的。

     使用例子如下:

     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             UserThread myThread = new UserThread("线程开启");
     6             Thread _nextThread = new Thread(myThread.WriteMessage);
     7             _nextThread.Start();//传入一个对象
     8             Console.WriteLine("主线程运行!");
     9             Thread.Sleep(20);
    10             _nextThread.Abort();//终止线程
    11             Console.WriteLine("线程终止!");
    12         }
    13     }
    14     class UserThread
    15     {
    16         private string message;
    17         public UserThread(string message)
    18         {
    19             this.message = message;
    20         }
    21         public void WriteMessage()
    22         {
    23             while (true)
    24             {
    25                 Console.WriteLine(message);
    26             }
    27 
    28         }
    29     }

    线程的控制


    在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。

    常用的属性和方法:

    1. 线程前后台的控制。在用Thread类创建线程的时候,可以设置IsBackground属性,表示它是一个前台线程还是一个后台线程。
    2. 线程的优先级。Thead类中设置Priority属性,以影响线程的基本优先级 ,Priority属性是一个ThreadPriority枚举定义的一个值。定义的级别有Highest ,AboveNormal,BelowNormal 和 Lowest
    3. 线程插入。可以调用Thread对象的Join方法,表示把Thread加入进来,停止当前线程,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止
    4. 终止线程。使用Thread对象的Abort()方法可以停止线程。
    5. 开始线程的。将当前实例的状态更改为 ThreadState.Running。
    6. 睡眠当前线程。Thread.Sleep()方法可以让当前线程休眠进入WaitSleepJoin状态

    线程争用问题:

    当程序较大时,运行程序的时候,在计算机有限的资源下,无法避免会产生多个线程争用的问题,对数据进行多次或没有修改。

    解决方案为使用Lock关键字,锁住引用对象。Lock只能锁引用对象!

    操作如下:

     

    当数据非应用类型,我们可以通过在对象初始化时,同时初始化一个object类型的变量sync,每次修改数据对象时都先锁定sync对象。

     

     

    使用例子:

    static void RaceCondition(object o ){
        StateObject state = o as StateObject;
        int i = 0;
        while(true){
            lock(state){
                state.ChangeState(i++);            
            }
    
        }
    }

    或者

    private object sync = new object();
    public void ChangeState(int loop){
        lock(sync){
            if(state==5){
                state++;
                Console.WriteLine("State==5:"+state==5+"  Loop:"+loop);
            }
            state = 5;
        }
    }

     

    线程死锁问题:

     同时出现两个锁,两个线程都在等另一个线程解锁。

     1 public class SampleThread{
     2     private StateObject s1;
     3     private StateObject s2;
     4     public SampleThread(StateObject s1,StateObject s2){
     5         this.s1= s1;
     6         this.s2 = s2;
     7     }
     8     public void Deadlock1(){
     9         int i =0;
    10         while(true){
    11             lock(s1){
    12                  lock(s2){
    13                     s1.ChangeState(i);
    14                     s2.ChangeState(i);
    15                     i++;
    16                     Console.WriteLine("Running i : "+i);
    17                 }
    18             }
    19         }
    20     }
    21     public void Deadlock2(){
    22         int i =0;
    23         while(true){
    24             lock(s2){
    25                  lock(s1){
    26                     s1.ChangeState(i);
    27                     s2.ChangeState(i);
    28                     i++;                                    Console.WriteLine("Running i : "+i);
    29                 }
    30             }
    31         }
    32     }
    33 }
    34 var state1 = new StateObject();
    35 var state2 = new StateObject();
    36 new Task(new SampleTask(s1,s2).DeadLock1).Start();
    37 new Task(new SampleTask(s1,s2).DeadLock2).Start();
    死锁现象

    解决方法就是一开始就设定好锁定的先后顺序。

    线程池


     线程池不需要自己创建,ThreadPool类是系统提供管理线程的线程池类。这个类会在需要时增减池中线程的线程数,直到达到最大的线程数。 池中的最大线程数是可配置的。

    创建线程需要时间。 如果有不同的小任务要完成,就可以事先创建许多线程 , 在应完成这些任务时发出请求。 这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。。

    在双核 CPU中 ,默认设置为1023个工作线程和 1000个 I/o线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。 如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。

    任务


    在.NET4 新的命名空间System.Threading.Tasks包含了类抽象出了线程功能,在后台使用的ThreadPool进行管理的。

    任务表示应完成某个单元的工作。这个工作可以在单独的线程中运行,也可以以同步方式启动一个任务。

    启动任务三种方法:

        ///TaskMethod表示一个委托方法
        /// <summary>
        /// 启动任务t1
        /// </summary>
        TaskFactory _Taskfactory = new TaskFactory();
        Task t1 = _Taskfactory .StartNew(TaskMethod);
    
        /// <summary>
        /// 启动任务t2
        /// </summary>
        Task t2 = TaskFactory.StartNew(TaskMethod);
    
        /// <summary>
        /// 启动任务t3
        /// </summary>
        Task t3 = new Task(TaskMethod);
        t3.Start();

    连续任务

    连续任务的特点是,连续任务的开启必要条件是上一个任务已经完成。也就是说:

    如果一个任务t1的执行是依赖于另一个任务t2的,那么就需要在这个任务t2执行完毕后才开始执行t1。这个时候我们可以使用连续任务。

     1     static void DoFirst(){
     2     Console.WriteLine("开始任务 : "+Task.CurrentId);
     3     Thread.Sleep(3000);
     4 }
     5     static void DoSecond(Task t){//t为上一个任务
     6     Console.WriteLine("任务"+t.Id+" finished.");//上一个任务已完成
     7     Console.WriteLine("this task id is "+Task.CurrentId);
     8     Thread.Sleep(3000);
     9 }
    10     static void Main(string[] args){
    11         Task t1 = new Task(DoFirst);
    12         Task t2 = t1.ContinueWith(DoSecond);
    13 }

    任务的层次结构

    我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion。

  • 相关阅读:
    HDU 2844 Coins(多重背包)
    HDU 4540 威威猫系列故事——打地鼠(DP)
    Codeforces Round #236 (Div. 2)
    FZU 2140 Forever 0.5
    HDU 1171 Big Event in HDU(DP)
    HDU 1160 FatMouse's Speed(DP)
    ZOJ 3490 String Successor
    ZOJ 3609 Modular Inverse
    ZOJ 3603 Draw Something Cheat
    ZOJ 3705 Applications
  • 原文地址:https://www.cnblogs.com/craft0625/p/7496682.html
Copyright © 2011-2022 走看看