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。

  • 相关阅读:
    小白扫盲之-计算机为何需要内存
    Centos 安装Pycharm 并移动到桌面。
    Docker守护进程
    插入排序
    快速排序
    归并排序
    __metaclass__方法
    Python面向对象(2)类空间问题以及类之间的关系
    Python面向对象(1)_初步认识
    python语法基础(8)_包
  • 原文地址:https://www.cnblogs.com/craft0625/p/7496682.html
Copyright © 2011-2022 走看看