C# 多线程(一)

(一)
通过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。只要  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类锁定对象:
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 (SynchronizationLockExcep tion e)
                    {
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedExcepti on 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 (SynchronizationLockExcep tion e)
                    {
                        //当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedExcepti on 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 quantity = 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 (ThreadInterruptedExcepti on 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);




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值