3

I am aware that this question is rather high-level and may be vague. Please ask if you need any more details and I will try to edit.

I am using QuickFix with Python bindings to consume high-throughput market data from circa 30 markets simultaneously. Most of computing the work is done in separate CPUs via the multiprocessing module. These parallel processes are spawned by the main process on startup. If I wish to interact with the market in any way via QuickFix, I have to do this within the main process, thus any commands (to enter orders, for example) which come from the child processes must be piped (via an mp.Queue object we will call Q) to the main process before execution.

This raises the problem of monitoring Q, which must be done within the main process. I cannot use Q.get(), since this method blocks and my entire main process will hang until something shows up in Q. In order to decrease latency, I must check Q frequently, on the order of 50 times per second. I have been using the apscheduler to do this, but I keep getting Warning errors stating that the runtime was missed. These errors are a serious issue because they prevent me from easily viewing important information.

I have therefore refactored my application to use the code posted by MestreLion as an answer to this question. This is working for me because it starts a new thread from the main process, and it does not print error messages. However, I am worried that this will cause nasty problems down the road.

I am aware of the Global Interpreter Lock in python (this is why I used the multiprocessing module to begin with), but I don't really understand it. Owing to the high-frequency nature of my application, I do not know if the Q monitoring thread and the main process consuming lots of incoming messages will compete for resources and slow each other down.

My questions:

  1. Am I likely to run into trouble in this scenario?

  2. If not, can I add more monitoring threads using the present approach and still be okay? There are at least two other things I would like to monitor at high frequency.

Thanks.

Community
  • 1
  • 1
Wapiti
  • 1,851
  • 2
  • 20
  • 40
  • 1
    `Queue.get` has a blocking-argument that if specified to be `False` will *not* block, but raise an exception you can easily catch. Have you tried that? Also, can you qualify "high frequency"? Is that the 30hz you mention earlier? Then IMHO you should have no troubles, even monitoring three sources. – deets Aug 23 '15 at 12:10
  • There is some throttling by my data provider, I think around the order of 18-30 milliseconds. That is roughly the smallest time between messages per market. During busy periods it's a fair amount of data. As for my own monitoring, I mentioned 50hz, rather than 30, but yes, I think that's probably sufficient for me. – Wapiti Aug 23 '15 at 12:14
  • 2
    to help you understand concurrency in Python, watch [David Beazley - Python Concurrency From the Ground Up: LIVE! - PyCon 2015](http://www.youtube.com/watch?v=MCs5OvhV9S4) – jfs Aug 23 '15 at 12:18
  • Then I truly doubt you'll run into any problems. BTW, how do you propagate your events into the mainloop? – deets Aug 23 '15 at 12:20
  • @deets I don't need to propagate into the main loop when using threading for some reason. If I get anything in `Q` I can call methods (like sending orders) directly from within the monitor program that checks `Q`. – Wapiti Aug 23 '15 at 13:28

2 Answers2

3

@MestreLion's solution that you've linked creates 50 threads per second in your case.

All you need is a single thread to consume the queue without blocking the rest of the main process:

import threading

def consume(queue, sentinel=None):
    for item in iter(queue.get, sentinel):
        pass_to_quickfix(item)

threading.Thread(target=consume, args=[queue], daemon=True).start()

GIL may or may not matter for performance in this case. Measure it.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • I'm not very familiar with the threading module or the niceties of concurrency so both your contributions are helpful. I know how to profile programs but not sure how I would measure GIL performance? – Wapiti Aug 23 '15 at 13:38
  • @Wapiti: you don't measure GIL performance (whatever it means); you measure the time performance of your program. Then while interpreting the results, you might conclude that GIL might explain the observed behavior or it may be explained using some other reason. You should know a couple of obvious things: different Python processes have their own GILs, Python may release GIL during I/O, various C extensions such as regex, numpy, lxml may also release GIL. – jfs Aug 23 '15 at 14:00
  • Thanks -- that's what I thought, but at the end of your answer I had the impression you were suggesting I measure the GIL. – Wapiti Aug 23 '15 at 14:08
0

Without knowing your scenario, it's difficult to say anything specific. Your question suggests, that the threads are waiting most of the time via get, so GIL is not a problem. Interprocess communication may result in problems much earlier. There you can think of switching to another protocol, using some kind of TCP-sockets. Then you can write the scheduler more efficient with select instead of threads, as threads are also slow and resource consuming. select is a system function, that allows to monitor many socket-connection at once, therefore it scales incredibly efficient with the amount of connections and needs nearly no CPU-power for monitoring.

Daniel
  • 42,087
  • 4
  • 55
  • 81
  • In the monitoring thread I am using `if Q.empty(): pass`, so it does not hang. But perhaps just calling `get` would be much better, for the reason you say? I am actually also using `ZeroMQ` to incorporate data from another source so have some socket stuff set up now. I am unaware of `select` though. Can you elaborate? – Wapiti Aug 23 '15 at 11:52
  • *"threads are also slow and resource consuming."* -- nonsense. Why do you think: (async.io) poll, poll, poll, poll, read, poll, read, poll, read is faster than (threads) blocking read, read, read? – jfs Aug 23 '15 at 12:15
  • @J.F.Sebastian: Oh, I see, you already managed a server with thousands of threads... I never said anything about polling, I said `select`. – Daniel Aug 23 '15 at 14:28
  • Thanks for the elaboration on `select`. That looks like something I should know about. – Wapiti Aug 23 '15 at 16:59