0

I have 15 threads and I want to execute 10 functions I pushed. I do not care about protecting it now, just wonder why win api executes function, but std does not.

Code is more or less from handmade hero day 123

struct WorkQueueEntry
{
    char* stringToPrint;
};
static uint32 nextEntryToDo;
static uint32 entryCount;
WorkQueueEntry entries[256];

inline void PushString(const char* string)
{
    WorkQueueEntry* entry = entries + entryCount++;
    entry->stringToPrint = const_cast<char*>(string);
}

struct ThreadInfo
{
    int logicalThreadIndex;
};

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    printf("entry count %i, nextEntryToDo %i\n", entryCount, nextEntryToDo);
    ThreadInfo* threadInfo = (ThreadInfo*)lpParameter;
    for(;;) {
        if(nextEntryToDo < entryCount) {
            WorkQueueEntry* entry = entries + nextEntryToDo++;
            char buffer[256];
            sprintf_s(buffer, "Thread %u: %s", threadInfo->logicalThreadIndex, entry->stringToPrint);
            printf(buffer);
        }
    }
}

somewhere in the main

#define WinThread


ThreadInfo threadInfo[5];
    for(int i = 0; i < _ARRAYSIZE(threadInfo); ++i) {
        ThreadInfo* info = threadInfo + i;
        info->logicalThreadIndex = i;

        #ifdef WinThread
        {
            DWORD threadID;
            HANDLE threadHandle = CreateThread(0, 0, ThreadProc, info, 0, &threadID);
            CloseHandle(threadHandle);
        }
        #else
        {
            std::thread t(ThreadProc, info);
            t.join();
        }
        #endif
    }

    PushString("String 0\n");
    PushString("String 1\n");
    PushString("String 2\n");
    PushString("String 3\n");
    PushString("String 4\n");
    PushString("String 5\n");
    PushString("String 6\n");
    PushString("String 7\n");
    PushString("String 8\n");
    PushString("String 9\n");

win api thread shows that at the begining of app entry count is 10, so if(nextEntryCount < entryCount) is true and funciton can be done. Std thread has entry count 0 at the begining, so if(nextEntryCount < entryCount) is not true and func cannot be done.

Why is that?

Pushing strings before creating std thread fixes it, but not exactly.

now it looks like this:

//#define WinThread

PushString("String 0\n");
PushString("String 1\n");
PushString("String 2\n");
PushString("String 3\n");
PushString("String 4\n");
PushString("String 5\n");
PushString("String 6\n");
PushString("String 7\n");
PushString("String 8\n");
PushString("String 9\n");

ThreadInfo threadInfo[5];
    for(int i = 0; i < _ARRAYSIZE(threadInfo); ++i) {
        ThreadInfo* info = threadInfo + i;
        info->logicalThreadIndex = i;

        #ifdef WinThread
        {
            DWORD threadID;
            HANDLE threadHandle = CreateThread(0, 0, ThreadProc, info, 0, &threadID);
            CloseHandle(threadHandle);
        }
        #else
        {
            std::thread t(ThreadProc, info);
            t.join();
        }
        #endif
    }

entry count is 10 at the begning and if(nextEntryCount < entryCount) is true, so func can be done, but it prints that work has been done by only thread with index of 0.

Why, using standard thread, only one thread has done all the work?

1 Answers1

6

t.join() makes you block until t finishes. CloseHandle(threadHandle) doesn't have the same effect (it closes the handle but doesn't wait on the thread).

If you want std::thread to match the WinAPI threads, create them all before joining any of them. The simplest solution would be to relinquish ownership of the thread without waiting on it, by replacing t.join() with t.detach().

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 4
    And if you *want* to join a WinApi thread, there's `WaitForSingleObject` (and friends). – Ben Voigt Mar 12 '19 at 23:50
  • Oh, I got it now, thank you. What about the one std thread that does all 10 funcitons? –  Mar 13 '19 at 06:46
  • @JarekBisnesu: That's because of the `join`. You launch one thread, then wait for it while it pulls each of the 10 work items off the queue and does them, before exiting, at which point `join` finally returns. The remaining threads are all launched sequentially, find they have nothing to do (because the first thread did it all), and immediately exit. – ShadowRanger Mar 13 '19 at 10:24
  • Is it ok to replace `join` with `detach` or I should find another way around with `join`? –  Mar 13 '19 at 15:13
  • 1
    @JarekBisnesu: `detach` and `join` are mutually exclusive (both of them require a `joinable` `thread`, and both of them render the `thread` they're called on non-`joinable`), so you can only use one or the other. Technically, using `detach` is [almost certain to invoke undefined behavior](https://stackoverflow.com/a/27796014/364696) without extreme efforts, but it's the mostly harmless sort of undefined behavior if you somehow guarantee the threads aren't doing anything important when `main` exits. – ShadowRanger Mar 13 '19 at 15:48
  • 1
    @JarekBisnesu: To use `join` for fully defined behavior, you'd need to store off all your threads as you create them, then `join` them all in a loop of the storage, e.g. make `std::thread allthreads[_ARRAYSIZE(threadInfo)];`, then in the existing loop, do `allthreads[i] = std::thread(ThreadProc, info);`, then make a new loop after the existing loop that just does something like `for (auto&& t : allthreads) t.join();`. In real life, I'd probably use a `vector` with `reserve` and `emplace_back` for all this, not stack arrays, but it should work either way; I just followed your existing code. – ShadowRanger Mar 13 '19 at 15:51
  • That's all I want to know. Thank you. –  Mar 13 '19 at 16:41