0

When I compile with the configuration set to release (for both x86 and x64), my program fails to complete. To clarify, there are no build errors or execution errors.

After looking for a cause and solution for the issue, I found Program only crashes as release build -- how to debug? which proposes that it is an array issue. Though this solve my problem, it gave me some insight on the matter (which I leave here for the next person).

To further muddle matters, it's only when a subroutine on the main thread has an execution time greater than about 0ms.

Here are the relevant sections of code:

//  Startup Progress Bar Thread
nPC_Current = 0; // global int
nPC_Max = nPC; // global int (max value nPC_Current will reach)

DWORD myThreadID;
HANDLE progressBarHandle = CreateThread(0, 0, printProgress, &nPC_Current, 0, &myThreadID);

/* Do stuff and time how long it takes (this is what increments nPC_Current) */

//  Wait for Progress Bar Thread to Terminate
WaitForSingleObject(progressBarHandle, INFINITE);

Where the offending line that my program gets stuck on is that last statement, where the program waits for the created thread to terminate:

WaitForSingleObject(progressBarHandle, INFINITE);

And here is the code for the progress bar function:

DWORD WINAPI printProgress(LPVOID lpParameter)
{   
    int lastProgressPercent = -1;   //  Only reprint bar when there is a change to display.

    //  Core Progress Bar Loop
    while (nPC_Current <= nPC_Max)
    {   
        // Do stuff to print a text progress bar
    }
    return 0;
}

Where the 'Core' while loop generally won't get a single iteration if the execution time of the measured subroutine is about 0ms. To clarify this, if the execution time of the timed subroutine is about 0ms, the nPC_Current will be greater than nPC_Max before the printProgressBar executes once. This means that thread will terminate before the main thread begins to wait for it.

If anyone would help with this, or provide some further insight on the matter, that would be fantastic as I'm having quite some trouble figuring this out.

Thanks!

edits:

  • wording
  • deleted distracting contents and added clarifications
Community
  • 1
  • 1
Zackary
  • 195
  • 9
  • Ooooooo! I HATE these! Release code is different from debug code, obviously. Very often something that works (or just seems to work) in the debug build is optimized out of existence in the release build, is initialized differently and exercises a different code path, or is reassembled in such a way that it exposes a logical flaw. I feel for you, Zackary, but there isn't much we can usually do without an [mcve] in a case like this. Building the MCVE is going to be an absolute , but on the upside it usually exposes the bug and saves you from a repost. – user4581301 Sep 30 '16 at 23:48
  • What does "my program fails to complete" mean? When built in _Debug_ mode, everything works OK? What does actually happen? – CristiFati Sep 30 '16 at 23:49
  • One thing to try is good old checkpoint debugging. pop in a few `std::cerr` statements to find out where in `printProgress` the code got stuck. – user4581301 Sep 30 '16 at 23:52
  • @CristiFati it can reach the end of main in debug mode, but gets stuck, I believe, waiting on a thread to terminate when it's already terminated in release mode (an issue it doesn't have in debug mode). – Zackary Sep 30 '16 at 23:57
  • Can you please show us the precise definition of `nPC_Current`; you say it is an `int`, but is it really an `int` ? – Mike Nakis Oct 01 '16 at 00:02
  • Are you certain it is already terminated? what does [`GetExitCodeThread`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683190(v=vs.85).aspx) say? – user4581301 Oct 01 '16 at 00:05
  • @user4581301 I know where it's getting stuck at, I'm just trying to figure out why and how to fix it. (Discussed in the question post) – Zackary Oct 01 '16 at 00:11
  • @MikeNakis to be precise, it's a (global) unsigned long long int (and so is nPC_Max) – Zackary Oct 01 '16 at 00:12
  • 2
    Ah, great. Well, there you have it: **it is *not* being incremented atomically.** – Mike Nakis Oct 01 '16 at 00:14
  • @Zackary: Are you sure your `nPC_Current` ever gets greater than `nPC_Max`? This is needed for the thread to terminate. If your "work" code stops incrementing `nPC_Current` at `nPC_Max`, the progress bar thread is left looping forever. – AnT stands with Russia Oct 01 '16 at 00:15
  • @user4581301 also, the exit code is always '259' – Zackary Oct 01 '16 at 00:17
  • @Zackary, you can also check if nPC_Max is being initialized differently if in release or debug... – Pedro Reis Oct 01 '16 at 00:17
  • 259: thread still active. Admittedly there is a window between the test and the wait where it could have terminated, but looks like the thread is still running. – user4581301 Oct 01 '16 at 00:19
  • hey, everyone: I do not see why we are still discussing this. Read my answer. I am asking the OP whether he is sure that nPC_Current gets incremented atomically, and I am explaining what will happen if it does not get incremented atomically. As it turns out, it is not really an int, it is a long, so it does not get incremented atomically, so at times his thread reads garbage. That's the answer. – Mike Nakis Oct 01 '16 at 00:19
  • @Mike Nakis: Your point is generally valid and should be taken into account in production-grade code, but it is not necessarily applicable to this practical experimental situation. The progress bar thread only reads the value of the counter, meaning that regardless of any havoc that might happen in between, eventually it should be able to see the stable final value of the counter. This stable final value is supposed to successfully terminate the thread. – AnT stands with Russia Oct 01 '16 at 00:22
  • @AnT I would say that it would be very precarious to ignore that. I can already start thinking of scenarios where a complete garbage read of nPC_Current leads to a "percentage" which is an astronomical value (anything but a percentage) and thus causes a *huge* string to be allocated. But my brain already hurts. I prefer fixing errors instead of hoping that they don't matter much. – Mike Nakis Oct 01 '16 at 00:26
  • @MikeNakis Sorry, there have been lots of comments and potential answers (more than I expected!) to sort through. And in my haste to test what the exit code would be, I made a mistake. It still is 259, but for the times > 0ms. And it does close (is 0) for the times about = 0ms – Zackary Oct 01 '16 at 00:28
  • @Mike Nakis: It can certainly lead to completely nonsensical garbage values, but it would probably be immediately detectable. My guess - infinite loop in release version because compiler believes `nPC_Current` is never modified. Missing `volatile`. – AnT stands with Russia Oct 01 '16 at 00:28
  • 1
    @Zackary: I have already successfully reproduced your exact problem using your original code. Yes, in release version of the code I saw the same behavior as you describe. The problem was immediately fixed in my case by declaring `nPC_Current ` as `volatile`. – AnT stands with Russia Oct 01 '16 at 00:29
  • As @Mike Nakis says, you are trying to read and write a global variable from more than one thread at once. If nPC_Current was an int, you might get away with this, but as a unsigned long long int you may not (in 32-bit code). The reason is that it may take more than one instruction to increment or change nPC_Current. While thread A is changing nPC_Current, thread B might try to read it, and will see a damaged value. The solution is to serialise access to your global using a mutex or similar. – Bids Oct 01 '16 at 00:30
  • @Bids I'm familiar with the concept of a mutex lock, but could you elaborate or provide an example? – Zackary Oct 01 '16 at 00:40
  • Try https://msdn.microsoft.com/en-us/library/windows/desktop/ms686927(v=vs.85).aspx. In C++ I would consider http://www.boost.org/doc/libs/1_31_0/libs/thread/doc/mutex.html to make life much easier. Out of interest, why use a thread at all? Your code is made much more complicated, and multi-threading is one of the most difficult programming topics to get right. – Bids Oct 01 '16 at 11:15
  • @Bids It's mostly about learning more. But, there is also an interest in trying to speed up my program, a personal project, as quickly as possible. The nPC_Max is using a unsigned long long int is because it is possible to have over 6E24 results, based on the input. (6E25 overflows that, so I've put in a hard limit to the length of the input to limit the output to 6E24). That is to say, the program can take a long time. So by implementing multi-threading, I can generally speedup my program. The progress bar was the first step. Now I want to integrate multi-threading with my algorithm. – Zackary Oct 01 '16 at 20:16
  • Though, as a side note, I've never tested it above an output of 3E12, as by that point it was already taking an entire day. – Zackary Oct 01 '16 at 20:18

3 Answers3

6

My guess would be that you forgot to declare your shared global variables volatile (nPC_Current specifically). Since the thread function itself never modifies nPC_Current, in the release version of the code the compiler optimized you progress bar loop into an infinite loop with never changing value of nPC_Current.

This is why your progress bar never updates from 0% value in release version of the code and this is why your progress bar thread never terminates.

P.S. Also, it appears that you originally intended to pass your nPC_Current counter to the thread function as a thread parameter (judging by your CreateThread call). However, in the thread function you ignore the parameter and access nPC_Current directly as a global variable. It might be a better idea to stick to the original idea of passing and accessing it as a thread parameter.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • See, I asked "What guarantees do you have that nPC_Current keeps incrementing over time?" and you found the precise technical reason why it wouldn't. – Mike Nakis Oct 01 '16 at 00:29
1

The number one rule in writing software is:

Leave nothing to chance; check for every single possible error, everywhere.

Note: this is the number one rule not when troubleshooting software; when there is trouble, it is already too late; this is the number one rule when writing software, that is, before there is even a need to troubleshoot.

There is a number of problems with your code; I cannot tell for sure that any one of those is what is causing you the problem that you are experiencing, but I would be willing to bet that if you fixed those, and if you developed the mentality of fixing problems like those, then you would not have the problem you are experiencing.

  1. The documentation for WaitForSingleObject says: "If this handle is closed while the wait is still pending, the function's behavior is undefined." However, you do not appear to be asserting that CreateThread() returned a valid handle. You are not even showing us where and how you are closing that handle. (And when you do close the handle, do you assert that CloseHandle() did not fail?)

  2. Not only you are using global variables, (which are something that I would strongly advice against,) but also, you happily make a multitude of assumptions about their values, without ever asserting any one of those assumptions.

    • What guarantees do you have that nPC_Current is in fact less than nPC_Max at the beginning of your function?

    • What guarantees do you have that nPC_Current keeps incrementing over time?

    • What guarantees do you have that the calculation of lastProgressPercent does not in fact keep yielding -1 during your loop?

    • What guarantees do you have that nPC_Max is not zero? (Division by zero on a separate thread is kind of hard to catch.)

    • What guarantees do you have that nPC_Max does not get also modified while your thread is running?

    • What guarantees do you have that nPC_Current gets incremented atomically? (I hope you understand that if it does not get incremented atomically, then at the moment that you read it from another thread, you may read garbage.)

You have tagged this question with [C++], and I do see a few C++ features being used, but I do not really see any object-oriented programming. The thread function accepts an LPVOID parameter precisely so that you can pass an object to it and thus continue being object-oriented in your second thread, with all the benefits that this entails, like for example encapsulation. I would suggest that you use it.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • 1
    You had a few valid points, and I've actually reworked my project to consider those (such that the original problem would have never existed). So, thanks for that. Edit: mostly that I created a struct for my globals and am now passing them as arguments, where it is easier to guarantee their state. – Zackary Oct 01 '16 at 21:01
-1

You can use (with some limitations) breakpoints in release...

Does this part of the code:

/* Do stuff and time how long it takes (this is what increments nPC_Current) */

depend on what printProgress thread does? (If so, you have to assure time dependence, and order conveniently) Are you sure this is always incrementing nPC_Current? Is it a time dependent algorithm? Have you tested the effect that a Sleep() has here?

Pedro Reis
  • 1,587
  • 1
  • 19
  • 19
  • This answers nothing. Ask for clarification in comments. – user4581301 Oct 01 '16 at 00:05
  • The reverse is true for the dependencies; printProgress is affected by what the 'stuff' is doing (as that increments affects the progress %). And I'm sure it's always incrementing nPC_Current (this has been thoroughly tested). The algorithm is also indepdent of time, I just wanted to time it to see how long it took. And sleep could work, but I was looking for something without delay. – Zackary Oct 01 '16 at 00:06
  • @Zackary, that dependency is clear, I was asking for the reverse: 'stuff' is affected by printProgress? – Pedro Reis Oct 01 '16 at 00:10
  • @Zackary, please try inserting a `Sleep()` just for test purpose, because if with this on, it doesn't fix the problem, we discard some situations. – Pedro Reis Oct 01 '16 at 00:14