Note that there are some explanatory texts on larger screens.

plurals
  1. POImprove efficiency and fairness when combining temporally close events
    primarykey
    data
    text
    <p>I have a bunch of threads that generate events of type <code>A</code> and type <code>B</code>.</p> <p>My program takes these events, wraps them in a message and sends them across the network. A&nbsp;message can hold either one <code>A</code> event, one <code>B</code> event, or one <code>A</code> event and one <code>B</code> event:</p> <pre><code>SendMessage(new Message(a: 1, b: null)); SendMessage(new Message(a: null, b: 2 )); SendMessage(new Message(a: 3, b: 4 )); </code></pre> <p>Events of type <code>A</code> happen quite frequently, while events of type <code>B</code> occur much less often. So, when a thread generates a <code>B</code> event, my program waits a bit to see if another thread generates an <code>A</code> event and combines the <code>A</code> event and the <code>B</code> event if possible.</p> <p>Here is my code:</p> <pre><code>object gate = new object(); int? pendingB; Message WrapA(int a, int millisecondsTimeout) { int? b; lock (gate) { b = pendingB; pendingB = null; Monitor.Pulse(gate); } return new Message(a, b); } Message WrapB(int b, int millisecondsTimeout) { lock (gate) { if (pendingB == null) { pendingB = b; Monitor.Wait(gate, millisecondsTimeout); if (pendingB != b) return null; pendingB = null; } } return new Message(null, b); } </code></pre> <p>This works so far. However, there are two problems:</p> <ul> <li><p>If there are lots of <code>A</code> events and lots of <code>B</code> events, the algorithm is not very efficient: Only a certain percentage of <code>B</code> events is attached to <code>A</code> events, even when there are enough <code>A</code> events.</p></li> <li><p>If there are no <code>A</code> events generated for a while (uncommon, but not impossible), the algorithm is completely unfair: One thread generating <code>B</code> events has to wait every time, while all other threads can send their <code>B</code> events right away.</p></li> </ul> <p>How can I improve efficiency and fairness of the algorithm?</p> <p><sub> Constraints:<br> &bullet;&nbsp; <code>WrapA</code> and <code>WrapB</code> must terminate within a short, deterministic amount of time.<br> &bullet;&nbsp; <code>SendMessage</code> must be called outside any locks.<br> &bullet;&nbsp; There is no synchronization mechanism available other than <code>gate</code>.<br> &bullet;&nbsp; There are not additional threads, tasks, timers, etc. available.<br> &bullet;&nbsp; Since events of type <code>A</code> happen so frequently in the normal case, busy-waiting in <code>WrapB</code> is fine. </sub></p> <hr> <p>Here is a test program that can be used as a benchmark:</p> <pre><code>public static class Program { static int counter0 = 0; static int counterA = 0; static int counterB = 0; static int counterAB = 0; static void SendMessage(Message m) { if (m != null) if (m.a != null) if (m.b != null) Interlocked.Increment(ref counterAB); else Interlocked.Increment(ref counterA); else if (m.b != null) Interlocked.Increment(ref counterB); else Interlocked.Increment(ref counter0); } static Thread[] Start(int threadCount, int eventCount, int eventInterval, int wrapTimeout, Func&lt;int, int, Message&gt; wrap) { Thread[] threads = new Thread[threadCount * eventCount]; for (int i = 0; i &lt; threadCount; i++) { for (int j = 0; j &lt; eventCount; j++) { int k = i * 1000 + j; int l = j * eventInterval + i; threads[i * eventCount + j] = new Thread(() =&gt; { Thread.Sleep(l); SendMessage(wrap(k, wrapTimeout)); }); threads[i * eventCount + j].Start(); } } return threads; } static void Join(params Thread[] threads) { for (int i = 0; i &lt; threads.Length; i++) { threads[i].Join(); } } public static void Main(string[] args) { var wrapper = new MessageWrapper(); var sw = Stopwatch.StartNew(); // Only A events var t0 = Start(10, 40, 7, 1000, wrapper.WrapA); Join(t0); // A and B events var t1 = Start(10, 40, 7, 1000, wrapper.WrapA); var t2 = Start(10, 10, 19, 1000, wrapper.WrapB); Join(t1); Join(t2); // Only B events var t3 = Start(10, 20, 7, 1000, wrapper.WrapB); Join(t3); Console.WriteLine(sw.Elapsed); Console.WriteLine("0: {0}", counter0); Console.WriteLine("A: {0}", counterA); Console.WriteLine("B: {0}", counterB); Console.WriteLine("AB: {0}", counterAB); Console.WriteLine("Generated A: {0}, Sent A: {1}", 10 * 40 + 10 * 40, counterA + counterAB); Console.WriteLine("Generated B: {0}, Sent B: {1}", 10 * 10 + 10 * 20, counterB + counterAB); } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload