Using the Strategy Pattern to Change Threading at Runtime

21 08 2011

The strategy pattern involves one class delegating some behaviour to a member which meets some interface. By delegating in this way you can change behaviour at runtime by changing the member to some other type from that same inheritance hierarchy. In my OrderMatchingEngine project I found a cool (to me at least) use for this pattern. You can change the behaviour of a component from synchronous, to multithreaded via the ThreadPool to multithreaded via dedicated heavyweight threads and back again as required at runtime.

In my project each OrderBook has an OrderProcessor. There are three types that inherit from OrderProcessor. They are SynchronousOrderProcessor, ThreadPooledOrderProcessor and DedicatedThreadsOrderProcessor. With some careful locking inside the OrderProcessor Property setter and the InserOrder method you can safely switch which type of OrderProcessor is used, committing more resources when and where they are needed.

On receipt of its first order the Market kicks off a timer which fires every second and prioritises the OrderBooks by number of orders received. The top 10% of OrderBooks are assigned dedicated threads, the next 20% use the ThreadPool and the remainder are given synchronous behaviour. In this way the Market can react to peaks of activity during the day and give more performance to those OrderBooks that need it.

namespace OrderMatchingEngine.OrderBook
{
public class SynchronousOrderProcessor : OrderProcessor
    {
        public SynchronousOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades)
            : base(buyOrders, sellOrders, trades)
        {
        }

        public override void InsertOrder(Order order)
        {
            ProcessOrder(order);
        }
    }

    public class ThreadPooledOrderProcessor : OrderProcessor
    {
        public ThreadPooledOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades)
            : base(buyOrders, sellOrders, trades)
        {
        }

        public override void InsertOrder(Order order)
        {
            ThreadPool.QueueUserWorkItem((o) => ProcessOrder(order));
        }
    }
    public class DedicatedThreadOrderProcessor : OrderProcessor
    {
        private readonly Thread m_Thread;
        private readonly BlockingCollection m_PendingOrders = new BlockingCollection();

        public DedicatedThreadOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades)
            : base(buyOrders, sellOrders, trades)
        {
            m_Thread = new Thread(ProcessOrders);
            m_Thread.Start();
        }

        private void ProcessOrders()
        {
            foreach (Order order in m_PendingOrders.GetConsumingEnumerable())
                ProcessOrder(order);
        }

        public void Stop()
        {
            m_PendingOrders.CompleteAdding();
        }

        public override void InsertOrder(Order order)
        {
            m_PendingOrders.Add(order);
        }
    }

    public class OrderBook
    {
        private OrderProcessor m_OrderProcessingStrategy;

        private readonly Object m_Locker = new Object();

        public Instrument Instrument { get; private set; }
        public BuyOrders BuyOrders { get; private set; }
        public SellOrders SellOrders { get; private set; }
        public Trades Trades { get; private set; }
        public Statistics Statistics { get; private set; }

        public OrderProcessor OrderProcessingStrategy
        {
            get { return m_OrderProcessingStrategy; }
            set
            {
                lock (m_Locker)
                {
                    var dedicatedThreadOrderProcessor = m_OrderProcessingStrategy as DedicatedThreadsOrderProcessor;

                    if (dedicatedThreadOrderProcessor != null)
                        dedicatedThreadOrderProcessor.Stop();

                    m_OrderProcessingStrategy = value;
                }
            }
        }

        public OrderBook(Instrument instrument, BuyOrders buyOrders, SellOrders sellOrders, Trades trades,
                         OrderProcessor orderProcessingStrategy)
        {
            if (instrument == null) throw new ArgumentNullException("instrument");
            if (buyOrders == null) throw new ArgumentNullException("buyOrders");
            if (sellOrders == null) throw new ArgumentNullException("sellOrders");
            if (trades == null) throw new ArgumentNullException("trades");
            if (orderProcessingStrategy == null) throw new ArgumentNullException("orderProcessingStrategy");
            if (!(instrument == buyOrders.Instrument && instrument == sellOrders.Instrument))
                throw new ArgumentException("instrument does not match buyOrders and sellOrders instrument");

            Instrument = instrument;
            BuyOrders = buyOrders;
            SellOrders = sellOrders;
            Trades = trades;
            OrderProcessingStrategy = orderProcessingStrategy;
            Statistics = new Statistics();
        }

        public OrderBook(Instrument instrument)
            : this(instrument, new BuyOrders(instrument), new SellOrders(instrument), new Trades(instrument))
        {
        }

        public OrderBook(Instrument instrument, BuyOrders buyOrders, SellOrders sellOrders, Trades trades)
            : this(
                instrument, buyOrders, sellOrders, trades, new SynchronousOrderProcessor(buyOrders, sellOrders, trades))
        {
        }

        public void InsertOrder(Order order)
        {
            if (order == null) throw new ArgumentNullException("order");
            if (order.Instrument != Instrument)
                throw new OrderIsNotForThisBookException();

            OrderReceived();

            //the strategy can change at runtime so lock here and in OrderProcessingStrategy property
            lock (m_Locker)
                OrderProcessingStrategy.InsertOrder(order);
        }

        private void OrderReceived()
        {
            var numOrders = Statistics[Statistics.Stat.NumOrders];
            numOrders++;
        }

        public class OrderIsNotForThisBookException : Exception
        {
        }
    }
}
Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: