确保线程安全
我正在编写一个 C# Windows 窗体应用程序,该应用程序通过算法(策略)处理市场报价,以向经纪公司创建订单。这一切似乎都经过了相当好的测试,直到我尝试建立在其自己的线程上同时运行多个策略的能力。此时一切开始运行不正确。我相信我有一些非线程安全的类,它们导致了不稳定的行为。任何关于如何以线程安全的方式线程化这个问题的见解都将受到深深的赞赏!
报价输入算法的方式如下: 1) 市场数据事件从经纪商软件发送到我的软件中名为 ConnectionStatus 的客户端类。当触发市场数据事件时,会根据这些表示买价、卖价等静态变量的当前值构建报价对象。 一旦构建了报价,我就会努力将其发送到每个正在运行的策略算法中。以下是我用来执行此操作的代码:
foreach (StrategyAssembler assembler in StrategyAssembleList.GetStrategies())
{
BackgroundWorker thread = strategyThreadPool.GetFreeThread();
if (thread != null)
{
thread.DoWork += new DoWorkEventHandler(assembler.NewIncomingQuote);
thread.RunWorkerAsync(quote);
}
}
StrategyAssembler 是一个类,它创建 StrategyManager 类的实例,而 StrategyManager 又创建包含实际算法的策略实例。可能有 4 或 6 个不同的 StrategyAssembler 实例,每个实例都已添加到 StrategyAssembleList 的 Singleton 实例中,该实例是 BindingList。
传入的报价对象被传递到 StrategyAssembler 类的 NewIncomingQuote 方法中。该代码如下:
public void NewIncomingQuote(object sender, DoWorkEventArgs e)
{
Quote QUOTE = e.Argument as Quote;
lock (QuoteLocker)
{
manager.LiveQuote(QUOTE);
priorQuote = QUOTE;
}
}
我认为通过在将引用传递到 manager.LiveQuote(Quote quote) 方法之前使用锁,所有使用该点“下游”引用的对象都将能够使用其中的引用线程安全的方式,但测试显示情况并非如此。有没有一种方法可以将 StrategyAssembler 的每个实例放在自己的线程上,以确保 Strategy Assembler 创建的所有对象都是线程安全的,然后将引用输入到 StrategyAssembler 中?这种思维方式是否适合应对这种情况?
预先感谢您的任何反馈或帮助,
Learning1
I am in the midst of writing a C# Windows Form application that processes quotes from the market through an algorithm (strategy) to create orders to a brokerage firm. That all seems to be testing fairly well until I tried to build in the capacity to run multiple strategies simultaneously with each strategy on it's own thread. At this point everything starts running incorrectly. I believe I have some classes that are not thread safe which are driving erratic behavior. Any insight on how I can thread this in a thread safe manner is deeply appreciated!
The way the Quotes are fed into the Algorithms is as Follows:
1) Market Data Events are fired from Brokers Software to a client class in my Software called ConnectionStatus. When the market Data event is triggered, a Quote object is built from the current values of these static variables that represent Bid, ask, etc.
Once the quote is built, I am endeavoring to send it into each of the Strategy Algorithms that are running. Here is the code I am using to do that:
foreach (StrategyAssembler assembler in StrategyAssembleList.GetStrategies())
{
BackgroundWorker thread = strategyThreadPool.GetFreeThread();
if (thread != null)
{
thread.DoWork += new DoWorkEventHandler(assembler.NewIncomingQuote);
thread.RunWorkerAsync(quote);
}
}
StrategyAssembler is a class that creates an instance of the Class StrategyManager which in turn creates an instance of the strategy that contains the actual algorithms. There may be 4 or 6 different instances of StrategyAssembler, each of which has been added to a Singleton instance of StrategyAssembleList which is a BindingList.
The incoming quote object is passed into the the NewIncomingQuote method of the StrategyAssembler Class. That code is as follows:
public void NewIncomingQuote(object sender, DoWorkEventArgs e)
{
Quote QUOTE = e.Argument as Quote;
lock (QuoteLocker)
{
manager.LiveQuote(QUOTE);
priorQuote = QUOTE;
}
}
I was thinking that by using a lock before passing the quote into the manager.LiveQuote(Quote quote) method that all the objects that use the quote "downstream" of this point would be able to consume the quote in a thread safe fashion, but testing is showing otherwise. Is there a way I could put the each instance Of StrategyAssembler on its own thread that ensures that all the objects created by Strategy Assembler are thread safe and then feed the quote into StrategyAssembler? Is this path of thinking an appropriate way to deal with this situation?
Thanks in advance for any feedback or help,
Learning1
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
锁定应该发生在读取和写入任何共享状态时。如果没有读锁,代码仍然可以并发读写。
您可以将读写锁定包装到管理器中。
The locking should occur at both the reading and writing to any shared state. Without locks on the read, the code can still read and write concurrently.
You could wrap the read and write locking into the manager.
如果:
1) 通过
LiveQuote
方法调用策略,并且可以修改Quote
实例。2) 对
Quote
实例的更改不应在策略之间共享。您需要在调用
LiveQuote()
之前创建所提供的Quote
的副本,并将该副本而不是原始报价发送到策略方法。根据其他要求,您可能根本不需要任何锁定。If:
1) Strategies are invoked via the
LiveQuote
method and can modifyQuote
instances.2) Changes to
Quote
instances should not be shared among strategies.You need to create a copy of the provided
Quote
before callingLiveQuote()
and send the copy in to the strategy method rather than the original quote. Depending on other requirements, you may not need any locking at all.您的代码中发生了两件事:
1. 您收到来自一个线程的报价(生产者又称为市场数据源)。
2. 您将报价发送到另一个线程(消费者又称为 StrategyAssembler)。
此时,报价上存在争用,换句话说,生产者线程和每个消费者线程(即策略的每个实例)都可以修改您刚刚提供的报价。为了消除争用,您必须执行以下三件事之一:
或者
或者
对于您的情况,我建议您采用第三个选项,因为锁定比复制报价更昂贵(我希望您的报价不是很大)...选项二也不错,但您的策略不应修改报价。
通过为每个消费者提供一份报价副本,您可以确保他们不会共享任何数据,因此其他线程不会修改报价,并且您将消除争用。如果您的策略没有创建任何其他线程,那么您就完成了。
一般来说,您应该避免锁定,并且应该尽量减少数据共享,但如果您必须在线程之间共享数据,那么您应该正确执行:
为了使您的策略正确同步,它们必须在同一个
QuoteLocker
对象上同步,即QuoteLocker
必须对每个线程可见。即使你做得正确并且使你的策略同步(锁定QuoteLocker)那么你也可能没有线程......你将运行上下文切换+锁定和你的策略的开销对于同一报价将按顺序执行。根据评论更新:
如果您按原样保留代码(意味着您为每个线程提供报价的副本),那么我不明白为什么您的其他策略在第一个策略完成之前不会获得报价...您的第一个策略将最可能在创建其他策略的线程时开始工作。让你的策略在单独的线程中运行的全部目的就是为了避免这个问题......你启动一个新线程,这样你的其他策略就不会等待彼此完成。
这部分代码很可能会在您的所有线程开始工作之前完成...
您的市场提要在您创建线程时是否更改了实际报价?市场提要通常会提供快照,因此除非在您制作线程时有什么东西改变了您的报价,否则上面的设计应该没问题。如果设计有问题,那么我可以为您提供基于阻塞队列的生产者和多个消费者设计,这也非常高效(您可以查看此讨论以了解其工作原理,我可以告诉您如何根据您的具体情况修改它例子)。
There are two things that are happening in your code:
1. You received a quote from one thread (the producer AKA the market data feed).
2. You send the quote to another thread (the consumer AKA StrategyAssembler).
At this point there is a contention on the quote, in other words the producer thread and each consumer thread (i.e. each instance of a strategy) can modify the quote which you just provide it with. In order for you to remove the contention you must do one of three things:
OR
OR
For your case I would suggest you take the third option because locking is more expensive than copying a quote (I hope your quotes are not really big)... option two is also good, but your strategy should not modify the quote.
By giving each consumer a copy of the quote you're ensuring that they don't share any data, therefore no other thread will modify the quote and you will eliminate contention. If your strategies are not creating any other threads, then you you're done.
In general you should avoid locking and you should try to minimize data sharing, but if you HAVE TO share data between threads then you should do it properly:
In order for your strategies to synchronize correctly they must synchronize on the same
QuoteLocker
object, i.e.QuoteLocker
must be visible to each thread. Even if you do it properly and you make your strategies synchronize (lock on theQuoteLocker
) then you might as well not have threads... you will be running the overhead of context switching + locking and your strategies will be executed sequentially for the same quote.Update per comments:
If you leave the code as is (meaning that you provide a copy of the quote for each thread), then I don't see why your other strategies will not get the quote until the first strategy completes... your first strategy will most likely begin working while the threads for the other strategies are being created. The whole point of making your strategies run in a separate thread is to avoid precisely that problem... you start a new thread so your other strategies are not waiting on each-other to complete.
This part of the code will most likely complete even before all your threads start working...
Is your market feed changing the actual quote while you're creating the threads? The market feeds generally provide snapshots, so unless something is changing your quote while you're making the threads, then the design above should be just fine. If there is a problem with the design, then I can give you a producer and multiple consumer design based on a blocking queue which is also very efficient (you can check out this discussion for an idea on how it works and I can tell you how to modify it for your specific example).