From the blog post related @Lambdageek, GHC Comment and GHC User Guide I am collecting the following:
The GHC tries to prevent revaluation of thunks, but since true blocking between threads is expensive, and thunks are usually clean and so harmless to reevaluate, it usually does it carelessly, with little chance of duplicating work anyway.
The method that he uses to avoid work is to replace the thunks with a black hole, a special marker that tells other threads (or sometimes the thread itself, as it happens when <<loop>> detected), which is evaluated by thunk.
Given this, there are at least three options:
By default, it uses the "lazy black checkholding", where it is done only before the thread is about to stop. Then he “walks” on his stack and creates “true” black holes for new tricks, using a lock to ensure that each thread receives only one thread scooping it, and to abort its own evaluation if it finds another thread, already scooped thunk. It’s cheaper because you don’t need to consider tricks whose evaluation time is so short that it completely coincides between two pauses.
Using -feager-blackholing-flag instead creates black holes as soon as the thunk evaluation starts, and the User Guide recommends this if you do a lot of parallelism. However, since locking on every thunk will be too expensive, these black holes are cheaper "impatient" that do not synchronize with other threads (although other threads can still see them if there is no race condition). Only when streams stop, do they turn into “true” black holes.
The third case, which the blog was especially worried about, is used for special functions, such as unsafePerformIO , for which it is harmful to evaluate thunk more than once. In this case, the stream uses a “true” black hole with a real lock, but creates it immediately by inserting an artificial thread into the pause before a real assessment.
Ørjan Johansen Aug 12 '15 at 19:48 2015-08-12 19:48
source share