【多线程】利用多线程提高并发吞吐率

       有关单线程想必不用在这里罗嗦;多线程常常应用在大量密集型事务处理、高并发以及性能亟待提升的敏感区域,好处不言而喻:充分利用CPU资源、提高吞吐、减少用户等待、同事增强了程序的灵活性,更有利于内存任务调度、数据交互、资源分配等; 但应用多线程,往往带来额外的复杂性,如:死锁、线程通讯、线程同步等等,暂且不用理会,具体问题具体分析便可。

      不论是.NET还是Java, 都提供了相对成熟的线程模型,只要在使用中控制好:线程的同步性、线程在特定场景的通讯以及线程的安全性就可以了。本文介绍以下几个场景下应用多线程的示例,以供参考!

§ 场景1

      远程服务数据查询接口,但碍于网络数据传输包大小的限制,每次只能查询最多N条数据,所以如果要显示或 提供100N条记录给用户就需要多次调用同一个接口,再进行封装为整体数据包给用户。

    public class Triple<K, V>
    {
        public K First { get; set; }
        public V Second { get; set; }
        public Triple()
        { }

        public Triple(K k, V v)
        {
            this.First = k;
            this.Second = v;
        }
    }

    public static List<Triple<Request, Exception>> TakeActionOn<Request>(this Action<Request> action, List<Request> requests)
            where Request : class
        {
            List<Triple<Request, Exception>> answers = new List<Triple<Request, Exception>>();
            using (WorkCountDown wcd = new WorkCountDown(requests.Count))
            {
                object syncLock = new object();
                foreach (Request request in requests)
                {
                    ThreadPool.QueueUserWorkItem(o =>
                    {
                        Request req = o as Request;
                        Triple<Request, Exception> answer = new Triple<Request, Exception>();
                        try
                        {
                            answer.First = req;
                            action(req);
                        }
                        catch (Exception ex)
                        {
                            ExceptionSendMgr.SendMonitor(ex);
                            answer.Second = ex;
                        }
                        finally
                        {
                            lock (syncLock)
                            {
                                answers.Add(answer);
                            }
                            wcd.CountDown();
                        }
                    }, request);
                }
                wcd.CountDown();
                wcd.WaitAll();
            }
            return answers;
        }

     /// <summary>
    /// Instead of WaitHandle.WaitAll's max to 64 limit
    /// </summary>
    class WorkCountDown : IDisposable
    {
        int totalCount;
        private readonly ManualResetEvent mre;
        public WorkCountDown(int totalWorkCount)
        {
            totalCount = totalWorkCount + 1;
            mre = new ManualResetEvent(false);
        }

        public void CountDown()
        {
            if (Interlocked.Decrement(ref totalCount) <= 0)
            {
                mre.Set();
            }
        }
        /// <summary>
        /// always remeber to call an extra CountDown before waitAll
        /// </summary>
        public void WaitAll()
        {
            mre.WaitOne();
        }

        #region IDisposable 成员

        public void Dispose()
        {
            ((IDisposable)mre).Dispose();
        }

        #endregion
    }

同时对于扩展参数:Action<Request> action,内部处理也需要关注线程安全、同步异步操作,当然可通过ThreadPool.SetMaxThreads来控制线程池大小。


§ 场景2

多线程下载,这块的应用相对复杂一些,总体来说就是:

       根据文件大小,对文件进行分块,同时通过建立对应的多个通道,进行并发下载,大大的提升下载效率,在下载过程主要存在的问题是:

1、 块下载状态的保存,存在下载暂停、中断等状况

2、 块合并

3、 各线程执行状况汇总,以便更新下载进度、下载报告(线程同步)

4、动态增加线程数


 

§多线程扩展

1、 ManualResetEvent

通常ThreadPool中的工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行,有关ManualResetEvent:

可以将布尔值传递给构造函数来初始化ManualResetEvent 的初始状态,true为终止状态;否则为 false。

在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

Reset()方法:将其设置为无信号状态;

Set()方法:将其设置为有信号状态。

WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作

2、InterLocked(线性):提供一些有用的原子操作,跟lock关键字在本质上是一样的,但是Lock为线性排他锁,所以不适宜关注性能的多线程并发处理,也算是悲观类型锁

3、互斥对象Mutex(线性)

     写过WIN32 App的朋友应该不会陌生的是,在APP启动时只允许一个实例运行时,比较简洁的处理方式也是通过Mutex信号量来实现

     进程之内或之间的线程需要访问操作系统的资源的时候,需要一个控制机制来限制资源访问的冲突都可以采用Mutex,同一时间,只能有一个线程占用Mutex。访问资源之前,每个线程都通过发信号,以获得Mutex的控制权。此后,线程还必须等待资源的控制权。当线程完成操作时,通过ReleaseMutex()发出完成信号( lock和Monitor对于unmanaged 资源是不起作用的)

Mutex objMutex = new Mutex(false, "ThreadLock" ); 
objMutex.WaitOne(); 
//代码
objMutex.ReleaseMutex();

适当的场景如果选择Mutex,可采用下面的helper来辅助完成:

/// <summary> 
/// 对managed/unmanaged资源进行加锁的类 
/// </summary> 
public class ThreadLockHelper
{
    static ThreadLockHelper mInstance = null;
    Mutex mMutex = null;
    private ThreadLockHelper() { }
    public static ThreadLockHelper GetInstance()
    {
        if (mInstance == null)
        {
            mInstance = new ThreadLockHelper();
            mInstance.mMutex = new Mutex(false, "ThreadLock");
        } return (mInstance);
    }
    public bool CreateLock()
    {
        if (mMutex == null)
        { mMutex = new Mutex(false, "ThreadLock"); }
        return (mMutex.WaitOne());
    }
    public void ReleaseLock()
    {
        mMutex.ReleaseMutex();
    }
}
ThreadLockHelper.GetInstance().CreateLock();         
objTask.DoTask();         
ThreadLockHelper.GetInstance().ReleaseLock()
 

4、静态方法属性[MethodImpl(MethodImplOptions.Synchronized)]

     指定同时只能由一个线程执行该方法。

     静态方法锁定类型,而实例方法锁定实例。在任何实例函数中只能有一个线程执行,并且在任何类的静态函数中只能有一个线程执行。

     有关Synchronized的应用,可详细参考:http://www.cnblogs.com/artech/archive/2008/10/17/1313209.html

5、Monitor

锁定和保护对象可以采用 lock(expression) statement_block expression:保护一个类的实例,一般地可使用this,保护一个静态变量属性(如互斥代码段在一个静态方法内部),一般使用类名就可以

当多线程公用一个对象时,也会出现和公用代码类似的问题而多线程过程中不能采用Lock,我们可以采用Monitor锁,只有得到锁的线程才可以操作

Queue oQueue=new Queue();
Monitor.Enter(oQueue);//oQueue对象只能被当前线程操纵,其他对象BLOCK
try
{
}
catch(){}
finally 
{
 Monitor.Exit(oQueue);//释放锁,其他线程可以操作
}

 

Monitor锁定后,内存存在预备队列、等待队列
Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列,锁被释放时,在预备队列中的线程可以立即获得对象锁
Monitor.Wait(); //释放对象上的锁并阻止当前线程,直到它重新获取该锁

以下为典型的生产者和消费者示例:(生产者写入一个,消费者读取一个,万变不离其宗,了解冲突解决办法即可):

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

    public class Cell
    {
        int cellContents; // Cell对象里边的内容
        bool readerFlag = false; // 状态标志,为true时可以读取,为false则正在写入
        public int ReadFromCell()
        {
            lock (this) // Lock关键字保证了什么,请大家看前面对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 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();//消费者从操作对象中读取信息
        }
    }

 

6、Thread相关

(1)IsBackground:true主线程结束,线程就自动结束,false主线程等待线程结束后才会结束,如果是Windows服务中启用该选项,

          或许你能看到有些事务未处理完时,APP无法停止

7、 其他

(1)5天不再惧怕多线程:http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2395279.html

(2)NET多线程探索-NET线程基础知识点:http://www.cnblogs.com/hailan2012/archive/2012/03/19/2405161.html

        NET多线程探索-线程同步和通信:http://www.cnblogs.com/hailan2012/archive/2012/03/20/2408179.html

        NET多线程探索-互斥锁,信号量,事件:http://www.cnblogs.com/hailan2012/archive/2012/03/22/2411934.html

(3)多线程读写锁:http://www.mysjtu.com/page/M0/S540/540055.html

(4)死锁:http://www.925lt.com/forum.php?mod=viewthread&tid=103

                      http://www.cnblogs.com/ols/articles/1179867.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值