Making Exceptions Work with OpenMP – Some Tiny Workarounds
In one of my last articles, I have told you about the state of affairs regarding exceptions and OpenMP. I also promised you a follow up-post that highlights some limited ways to work around some of these problems from a user perspective. This is it. Be warned: it is going to be short and sweet, because there is not really much you can do (or if there is, I am too dumb to find out). 😉
Let’s start with a short recap of some of the points I was trying to make in my last article:
- all exceptions need to be caught before the end of the parallel region, or else the code is not conforming
- no exceptions can be thrown out of a structured block associated with most OpenMP-constructs; if an exception is thrown inside such a structured block it must be caught before the block is left
- barriers and exceptions don’t play well together
There is not much that can be done about the first point, unfortunately. One might be tempted to try to catch all exceptions at the end of each thread manually, safe them away and re-throw them in the main thread – but unfortunately this does not work. C++ gives you the ability to catch an exception of any type via catch(…), but if you use this, there is no way to access the exception in the catch-block and save it – and therefore no way to re-throw it elsewhere either.
So no luck for the first point – let’s try the second: throwing exceptions out of structured blocks associated with OpenMP-constructs. Which constructs does this apply to? There are the worksharing constructs: for, sections, single and the synchronization constructs: critical, master, ordered (only valid inside for). The latter are easier, therefore I will start with them. I have already told you a solution for the critical-construct in one of my earlier articles about scoped locking: if you need to throw an exception from inside a critical region, don’t use critical but try guard objects instead. Actually, the elegance of this solution was the reason for exploring exceptions and OpenMP further: Would it be possible to apply the core idea of Resource Acquisition is Initialization to other OpenMP-constructs and create exception-safe alternatives?
Unfortunately, the answer I have found is: No. Or at least not for many constructs. But I won’t get ahead of myself and tell you what works first, before going into more details about what does not work and why. It is possible for a programmer to write an exception-safe master-directive. I bet many compilers implement the master directive like this:
[CPP]
if (omp_get_thread_num () == 0) {
// do whatever inside the master region
}
[/CPP]
And there is nothing that stops you (the OpenMP-programmer) from doing so as well. There is no problem with exceptions in this code, either, as it is only forbidden to throw exceptions out of scopes attached to OpenMP-constructs. Which is not the case here. A negative side-effect of this change is that many of the available tools to help you spot mistakes in your OpenMP-code (I have linked to them more than enough already) don’t like distributing work using thread-numbers and will not check this code. Therefore I generally advise my students to use the master construct, but if you need to throw exceptions there it may be worth ignoring that advice.
That’s about the end of this post, unfortunately. These are the only two constructs that I can come up with a solution for OpenMP-programmers. For all other constructs I don’t have any solutions. Of course, it is possible to e.g. rewrite a sections-worksharing-construct using manual work distribution. But what cannot be done is to trick the implied barrier at the end of it. Which brings me to the third point in my former article: barriers. I have spent a considerable amount of time thinking about ways to implement exception-safe barriers manually (when I say exception-safe in this context, I mean a barrier that recognizes automatically when one of the parallel regions threads has left the current scope and lets all other threads waiting on the barrier pass in this case). Unfortunately, I have not come up with an acceptable solution. And even if I did, the question remains: would such a barrier be intuitive and easy to use? After presenting some of my ideas to my colleagues, I have come to the conclusion that this is not the case. If I cannot solve the barrier-problem, all the worksharing-constructs are hopeless as well, because they have an implied barrier at the end.
Maybe, some day, when I am older, more experienced and possibly even smarter (do you become smarter with age? Boy, that would be nice 😀 ), I will come up with a solution that makes sense on all levels (usability included). Until then, you will have to cope with the two poor results of this article:
- if you want to throw an exception out of a critical-region in OpenMP – use guard objects (scoped locking)
- if you want to throw an exception out of a master region in OpenMP – use if (omp_get_thread_num () == 0)
- if you want to throw an exception out of any other scope that was opened by an OpenMP-construct, you are out of luck
That’s all I have to offer as workarounds in this post (now that I look at this poor and relatively uninformative piece, I am hesitant to even call it an article). And this also closes my short trip into the world of exceptions and OpenMP. I believe anything above the ideas I have sketched needs to be delivered on a language-level – and since I don’t know much about the inner workings and abilities of C++ compilers, I am not qualified to comment on that.