, 18 tweets, 4 min read
My Authors
Read all threads
1/ So about 5 years ago I came up with something called the SingleUseCriticalSection. It's been happily guarding access to lazily initialized data (basically Lazy<T>) in Paint​.NET for about 5 years now, so I thought I'd share it: pastebin.com/1kx47Yw7
2/ Using Monitor (aka lock(object)) in .NET is pretty bad when there's lots of contention because you burn A LOT of CPU time on spin locks.

It's just terrible. A kernel-space sync primitive that actually sleeps the threads during contention is necessary
3/ You can use a Win32 CRITICAL_SECTION with a spin count of zero with docs.microsoft.com/en-us/windows/… , and I do make use of these a lot in the app. Entering the C_S when it's already locked will immediately sleep the thread. This is good.
4/ However, you need to hold onto these for the whole lifetime of the guarded object (the deferred/lazily initialized thing). It'd be nice to free the sync object when it's no longer needed ...
5/ ... and it'd be REALLY nice to avoid the lock convoy that happens: once the lazy init is completed by the first thread, it releases the lock, all other threads wake up -- 1 at a time! -- and see the data is ready. It's really bad when it happens frequently (e.g. 60 fps).
6/ SingleUseCriticalSection makes use of a ManualResetEvent that is Set() and then null'd once the first Enter/Exit pair is completed.
7/ The first thread gets the lock pretty much for free, with only an Interlocked increment and a normal field write. Subsequent threads Wait() on the event and go to sleep.

Once first thread is done, it signals the event with Set() and then nulls the field.
8/ The Set() releases ALL waiting threads simultaneously, and the null allows all future access to skip the Wait() call, avoiding kernel transition and making the ManualResetEvent eligible for GC.

Further attempts to enter only cost an interlocked increment and regular read.
9/ Care must be exercised because once the aptly named SINGLE USE Critical Section has been used, it's basically a no-op and doesn't provide any synchronization at all. And this is exactly what we want!
10/ Some of the field access is "sloppy," but that's okay.

Is the ownerThreadID field write not done yet or not visible to other threads/CPUs yet? No biggie, it really only needs validity on the thread that got the lock so as to support reentrancy.
11/ What if the ManualResetEvent being set to null isn't yet visible to some other thread that is trying to enter? No biggie, it's signaled and we just end up with a Wait() call that completes immediately.

Importantly, the ManualResetEvent is NOT disposed. GC must clean up.
12/ As long as the memory model of the CPU and runtime (etc.) provide a few simple guarantees, it works out from a correctness standpoint.
13/ A typical scenario in PDN is rendering some graphics with an alpha mask. The mask is rasterized from selection polygon w/ antialiasing. However, first section of mask init is not parallelizable. So, all rendering threads must wait for it.
14/ It's not just alpha masks that cause this scenario. Any data that is needed for rendering but that shouldn't be calculated on the UI thread can be guarded this way.

Polygons need clipping, combining, transforming. Text needs layout. Brushes need to be loaded, rendered. etc
If you want to use that code, go for it: I consider it public domain.

Also I made a mistake in my paste: Enter() and Exit() should not have 'override'. In my code base, the class derives from a base CriticalSection class.
This is the sort of thing I think about when I see new CPUs with ever increasing core counts. Software will need more tools to ensure effective multithreaded scaling
Blindly using Monitor or other simple locks is just asking for 100% usage across all cores with 99% being wasteful spinlocking
Here's the fixed code: pastebin.com/UxMQF9uf
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with Rick Brewster

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!