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。

 

posted @ 2017-09-13 16:16  20世纪少年  阅读(1747)  评论(0编辑  收藏  举报