您的位置:首页 > 编程语言 > C#

C# 多线程(一)

2015-10-22 11:27 309 查看
(一)
通过System.Threading命名空间中提供的Thread类来创建和控制线程,ThreadPool类用于管理线程池等;
Thread类有几个至关重要的方法,描述如下:
Start(); 启动线程
Sleep(int); 静态方法,暂停当前线程制定的毫秒数
Abort(); 通常使用该方法来终止一个线程
Join(); 等待直到线程结束
Suspend(); 该方法并不终止未完成的线程,它仅仅挂起线程,以后还可以恢复
Resume(); 恢复被Suspend()方法挂起的线程执行。

(二)如何操纵一个线程
使用Thread类创建线程时,只需要提供线程入口即可。

在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,当调用Thread.Start()方法后,线程就开始执行ThreadStart所指向的函数。

Thread oThread = new Thread(new
ThreadStart(oAlpha.Beta));
     
     
oThread.Start();
     
      while
(!oThread.IsAlive) ;
     
      {
     
     
    Thread.Sleep(2);
     
     
    oThread.Abort();
     
     
    oThread.Join();
     
     
    Console.WriteLine();
     
     
    Console.WriteLine("Alpha.Beta
has finished");

     
     
    try
     
     
    {
     
     
     
  Console.WriteLine("Try to restart the Alpha.Beta
thread");
     
     
     
  oThread.Start();
     
     
    }
     
     
    catch
(ThreadStateException)
     
     
    {
     
     
     
  Console.WriteLine("ThreadStateException trying
to restart Alpha.Beta.");
     
     
     
  Console.WriteLine("Exception since aborted
threads cannot be restarted.");
     
     
     
  Console.ReadKey();
     
     
    }
     
      }

在创建线程oThread时,我们用指向oAlpha.Beta()方法初始化了ThreadStart代理(delegate)对象,当我们创建线程oThread调用oThread.Start()方法启动时,实际上程序运行的是oAlpha.Beta()方法。

在Main方法中的while循环中,使用静态方法Thread.Sleep()让主线程暂停一段时间,这段时间CPU转向执行线程oThread。然后我们使用Thread.Abort()方法终止了线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。也可以给Thread.Join()方法指定一个int型的参数作为最长的等待时间。

之后我们试图用Thread.Start方法重新启动线程oThread,但是,显然Abort()方法终止后的线程不可恢复,所以程序最后抛出ThreadStateException异常。

Thread.ThreadState 属性
这个属性代表了程序运行时的状态。
ThreadState属性的取值如下:
Aborted 线程已停止
AbortRequested 线程的Thread.Abort()方法已被调用,但是线程还未停止
Background 线程在后台执行,与属性Thread.IsBackground有关
Running 线程正在正常运行
Stopped 线程已经被停止
StopRequested 线程正在被要求停止
Suspended 线程已经被挂起(在此状态下,可以通过Resume()方法重新运行)
SuspendRequest 线程正在要求被挂起,但是还未来得及相应
Unstarted 未调用Thread.Start()开始线程的运行
WaitSleepJoin 线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态。

注:后台线程与前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,CLR(通用语言运行环境)将调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。

线程的优先级

当线程之间争夺CPU时间时,CPU是按照线程的优先级给予服务的。在C#程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。

(三)生产者和消费者

每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。几个线程同时执行一个函数,会导致数据混乱,产生不可预料的结果。必须避免这种情况的发生。
C#提供了一个关键字lock,可以把一段代码定义为互斥段(critical
section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义入下:
lock(expression) statement_block

expression代表你希望跟踪的对象,通常是对象引用。如果你想保护一个类的实例,一般的,可以使用this;保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
statement_block就是互斥代码段,这段代码在一个时刻内只可能被一个线程执行。

lock语句(C#)MSDN  http://msdn.microsoft.com/zh-cn/library/c5kehkcz(v=vs.100).aspx
下例使用线程和 lock。只要 lock 语句存在,语句块就是临界区并且 balance 永远不会是负数。

using System;
using System.Threading;

class Account
{
    private
Object thisLock = new Object();
    int
balance;

    Random r
= new Random();

    public
Account(int initial)
   
{
   
    balance =
initial;
   
}

    int
Withdraw(int amount)
   
{

   
    // This condition will never
be true unless the lock statement
   
    // is commented
out:
   
    if (balance <
0)
   
    {
   
     
  throw new Exception("Negative
Balance");
   
    }

   
    // Comment out the next line
to see the effect of leaving
out 
   
    // the lock
keyword:
   
   
lock(thisLock)
   
    {
   
     
  if (balance >= amount)
   
     
  {
   
     
     
Console.WriteLine("Balance before Withdrawal :  "
+ balance);
   
     
     
Console.WriteLine("Amount to Withdraw  
     : -" +
amount);
   
     
      balance =
balance - amount;
   
     
     
Console.WriteLine("Balance after Withdrawal  :
 " + balance);
   
     
      return
amount;
   
     
  }
   
     
  else
   
     
  {
   
     
      return 0;
// transaction rejected
   
     
  }
   
    }
   
}

    public
void DoTransactions()
   
{
   
    for (int i = 0; i < 100;
i++)
   
    {
   
     
  Withdraw(r.Next(1, 100));
   
    }
   
}
}

class Test
{
    static
void Main()
   
{
   
    Thread[] threads = new
Thread[10];
   
    Account acc = new
Account(1000);
   
    for (int i = 0; i < 10;
i++)
   
    {
   
     
  Thread t = new Thread(new
ThreadStart(acc.DoTransactions));
   
     
  threads[i] = t;
   
    }
   
    for (int i = 0; i < 10;
i++)
   
    {
   
     
  threads[i].Start();
   
    }
   
}
}

Monitor类锁定对象:
MSDN:http://msdn.microsoft.com/zh-cn/library/system.threading.monitor(v=vs.100).aspx
Monitor常用的方法:
Enter() 锁定对象
Exit() 释放对象
Pulse() 通知等待队列中的线程锁定对象状态的改变
Wait() 释放对象上的锁并组织当前线程,直到它重新获取该锁

注1:当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。

注2:lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。(这就是lock与Enter及Exit的关系)

当多线程公用一个对象时,应使用Monitor类,其提供了使线程共享资源的方案。Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。保证了一个时刻只有一个线程可以访问这个对象。Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有办法都是静态的,不能使用对象来引用。下面的代码说明了使用Monitor锁定一个对象的情形:
。。。
Queue oQueue=new Queue();
....
Monitor.Enter(oQueue);
....//现在oQueue对象只能被当前线程操纵了
Monitor.Exit(oQueue);//释放锁

其他线程只有等待它使用Monitor.Exit()方法释放锁后才可以使用。为了保证线程最终都能释放锁,可以把Monitor.Exit()方法写在try-catch-finally结构的finally代码块里。

对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:现在持有锁的线程的引用;预备队列,队列中保存了已经准备好获取锁的线程;等待队列,队列中保存着当前正在等待这个对象状态改变的线程的引用。

当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。

下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。这个历程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示:

namespace Ex2
{
    public class Cell
    {
     
  int cellContents;  
     
  //Cell对象中的内容
     
  bool readerFlag = false;  
 //a状态标志,true可以读取,为false时则正在写入
     
  public int ReadFromCell()
     
  {
     
      lock
(this)  //lock关键字保证其他线程不能够访问该程序块
     
      {
     
     
    if (!readerFlag)
     
     
    {
     
     
     
  try
     
     
     
  {
     
     
     
     
//等待WriteToCell方法中调用Monitor.Pulse()方法
     
     
     
     
Monitor.Wait(this);
     
     
     
  }
     
     
     
  catch (SynchronizationLockException e)
     
     
     
  {
     
     
     
     
Console.WriteLine(e);
     
     
     
  }
     
     
     
  catch (ThreadInterruptedException e)
     
     
     
  {
     
     
     
     
Console.WriteLine(e);
     
     
     
  }
     
     
    }
     
     
    Console.WriteLine("Consume:
{0}", cellContents);
     
     
    readerFlag = false;
     
     
   
//重置readerFlag标志,表示消费行为已经完成
     
     
    Monitor.Pulse(this);
     
     
   
//通知WriteToCell()方法,(该方法在另外一个线程中执行,等待中)
     
      }
     
      return
cellContents;
     
  }

     
  public void WriteToCell(int n)
     
  {
     
      lock
(this)
     
      {
     
     
    if (readerFlag)
     
     
    {
     
     
     
  try
     
     
     
  {
     
     
     
     
Monitor.Wait(this);
     
     
     
  }
     
     
     
  catch (SynchronizationLockException e)
     
     
     
  {
     
     
     
     
//当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
     
     
     
     
Console.WriteLine(e);
     
     
     
  }
     
     
     
  catch (ThreadInterruptedException e)
     
     
     
  {
     
     
     
     
//当线程在等待状态的时候中止
     
     
     
     
Console.WriteLine(e);
     
     
     
  }
     
     
    }
     
     
    cellContents = n;
     
     
    Console.WriteLine("Produce:
{0}", cellContents);
     
     
    readerFlag = true;
     
     
    Monitor.Pulse(this);
     
     
   
//通知另外一个线程中正在等待的ReadFromCell()方法
     
      }
     
  }
    }

    public class
CellProd
    {
     
  Cell cell;    
   
 //被操作的Cell对象
     
  int quantity = 1;  
//生产者次数,初始化为1

     
  public CellProd(Cell box, int request)
     
  {
     
     
//构造函数
     
      cell =
box;
     
      quantity =
request;
     
  }

     
  public void ThreadRun()
     
  {
     
      for (int
looper = 1; looper <= quantity; looper++)
     
     
    cell.WriteToCell(looper);
  //生产者向操作对象写入信息
     
  }
    }

    public class
CellCons
    {
     
  Cell cell;
     
  int quantit
af95
y = 1;

     
  public CellCons(Cell box, int request)
     
  {
     
      cell =
box;
     
      quantity =
request;
     
  }

     
  public void ThreadRun()
     
  {
     
      int
valReturned;
     
      for (int
looper = 1; looper <= quantity; looper++)
     
     
    valReturned =
cell.ReadFromCell();  //消费者从操作对象中读取信息
     
  }
    }

    class program
    {
     
  public static void Main(String[] args)
     
  {
     
      int result
= 0;    
//一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
     
      Cell cell
= new Cell();

     
     
//下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
     
      CellProd
prod = new CellProd(cell, 20);
     
      CellCons
cons = new CellCons(cell, 20);

     
      Thread
producer = new Thread(new ThreadStart(prod.ThreadRun));
     
      Thread
consumer = new Thread(new ThreadStart(cons.ThreadRun));
     
     
//生产者线程和消费者线程都已经被创建,但是没有开始执行

     
      try
     
      {
     
     
    producer.Start();
     
     
    consumer.Start();

     
     
    producer.Join();
     
     
    consumer.Join();

     
     
    Console.ReadKey();
     
      }
     
      catch
(ThreadStateException e)
     
      {
     
     
   
//当线程因为所处状态的原因而不能执行被请求的操作
     
     
    Console.WriteLine(e);
     
     
    result = 1;
     
      }
     
      catch
(ThreadInterruptedException e)
     
      {
     
     
    //当线程在等待状态的时候中止
     
     
    Console.WriteLine(e);
     
     
    result = 1;
     
      }
     
   
  
     
     
//尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
     
     
Environment.ExitCode = result;
     
  }
    }
}

在上面的例程中,同步是等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulse()发送的“脉冲”。

程序执行过程:线程通过 lock(this)
即Enter方法获得对象cell的锁(或者等待获得),首先判断数据是否存在,即readFlag的值。
如果为false,即没有数据可以读取,producer线程执行写数据操作,即 cellContents =
n;Console.WriteLine("Produce: {0}", cellContents);
 同时设置readerFlag为真,并通知等待队列中的consumer线程。readerFlag =
true; Monitor.Pulse(this);
consumer线程则执行Wait(),等待producer线程执行完后,将锁再次归还consumer线程。

如果为true,即有数据可以读取,producer线程执行Wait(),等待consumer线程执行完后,将锁再次归还producer线程;consumer线程执行读数据操作,即Console.WriteLine("Consume:
{0}", cellContents);
同时设置readerFlag为假,并通知等待队列中的producer线程。readerFlag =
false; Monitor.Pulse(this);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: