none
HeapAlloc() function and 3 threads RRS feed

  • Question

  • I would like to expand the WinAPI console application that draws random numbers and calculates the average, maximum and minimum in one thread to three separate threads for each function.
    I use below tutorial, among others, and I wonder if it is necessary to allocate a block of memory from a heap using the HeapAlloc() and HeapFree() functions? I can not find anything satisfactory on this topic in the  provided materials.
    Could someone explain in simple words if and why such allocation is required?
    How such allocation should be properly done for 3 threads using HeapAlloc()?
    In the next step i would like to test critical section for thread synchronization.
    A slightly modified sample code using semaphores below.

    https://www.bogotobogo.com/cplusplus/multithreading_win32B.php

    #include <Windows.h>
    #include <process.h>
    #include <iostream>
    
    using namespace std;
    
    HANDLE semaphore;
    int count1 = 0;
    int count2 = 0;
    int count3 = 0;
    
    void addCount(int increment)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count1 += increment;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void subCount(int decrement)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count2 -= decrement;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void mulCount(int multiply)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count3 *= multiply;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    unsigned int __stdcall mythread1(void*)
    {
        addCount(12);
        return 0;
    }
    
    unsigned int __stdcall mythread2(void*)
    {
        subCount(56);
        return 0;
    }
    
    unsigned int __stdcall mythread3(void*)
    {
        mulCount(22);
        return 0;
    }
    
    int main(int argc, char* argv[])
    {
        HANDLE myhandleA, myhandleB, myhandleC;
    
        semaphore = CreateSemaphore(0, 1, 1, 0);
    
        myhandleA = (HANDLE)_beginthreadex(0, 0, &mythread1, (void*)0, 0, 0);
        myhandleB = (HANDLE)_beginthreadex(0, 0, &mythread2, (void*)0, 0, 0);
        myhandleC = (HANDLE)_beginthreadex(0, 0, &mythread3, (void*)0, 0, 0);
    
        WaitForSingleObject(myhandleA, INFINITE);
        WaitForSingleObject(myhandleB, INFINITE);
        WaitForSingleObject(myhandleC, INFINITE);
    
        CloseHandle(myhandleA);
        CloseHandle(myhandleB);
        CloseHandle(myhandleC);
    
        cout << "Count1 = " << count1 << endl;
        cout << "Count2 = " << count2 << endl;
        cout << "Count3 = " << count3 << endl;
    
        CloseHandle(semaphore);
    
        return 0;
    }
    Monday, September 9, 2019 11:22 AM

All replies

  • I would like to expand the WinAPI console application that draws random numbers and calculates the average, maximum and minimum in one thread to three separate threads for each function.
    I use below tutorial, among others, and I wonder if it is necessary to allocate a block of memory from a heap using the HeapAlloc() and HeapFree() functions? I can not find anything satisfactory on this topic in the  provided materials.
    Could someone explain in simple words if and why such allocation is required?
    How such allocation should be properly done for 3 threads using HeapAlloc()?
    In the next step i would like to test critical section for thread synchronization.
    A slightly modified sample code using semaphores below.

    https://www.bogotobogo.com/cplusplus/multithreading_win32B.php

    #include <Windows.h>
    #include <process.h>
    #include <iostream>
    
    using namespace std;
    
    HANDLE semaphore;
    int count1 = 0;
    int count2 = 0;
    int count3 = 0;
    
    void addCount(int increment)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count1 += increment;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void subCount(int decrement)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count2 -= decrement;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void mulCount(int multiply)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count3 *= multiply;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    unsigned int __stdcall mythread1(void*)
    {
        addCount(12);
        return 0;
    }
    
    unsigned int __stdcall mythread2(void*)
    {
        subCount(56);
        return 0;
    }
    
    unsigned int __stdcall mythread3(void*)
    {
        mulCount(22);
        return 0;
    }
    
    int main(int argc, char* argv[])
    {
        HANDLE myhandleA, myhandleB, myhandleC;
    
        semaphore = CreateSemaphore(0, 1, 1, 0);
    
        myhandleA = (HANDLE)_beginthreadex(0, 0, &mythread1, (void*)0, 0, 0);
        myhandleB = (HANDLE)_beginthreadex(0, 0, &mythread2, (void*)0, 0, 0);
        myhandleC = (HANDLE)_beginthreadex(0, 0, &mythread3, (void*)0, 0, 0);
    
        WaitForSingleObject(myhandleA, INFINITE);
        WaitForSingleObject(myhandleB, INFINITE);
        WaitForSingleObject(myhandleC, INFINITE);
    
        CloseHandle(myhandleA);
        CloseHandle(myhandleB);
        CloseHandle(myhandleC);
    
        cout << "Count1 = " << count1 << endl;
        cout << "Count2 = " << count2 << endl;
        cout << "Count3 = " << count3 << endl;
    
        CloseHandle(semaphore);
    
        return 0;
    }
    In the posted code I can see no reason why the heap functions HeapAlloc and HeapFree would be relevant to performing arithmetic operations on global variables.

    The purpose for using synchronization objects such as semaphores, mutexes, etc. is to ensure the integrity of a resource that may be accessed by multiple threads.

    However, in the code snippet above each thread accesses a resource that is not shared between threads.  In other words, thread mythread1 is the only thread that calls the addCount function that accesses global variable count1, thread mythread2 is the only thread that calls the subCount function that accesses global variable count2 and thread mythread3 is the only thread that calls the mulCount function that accesses global variable count3.  So in the posted code no global variable could possibly be accessed by more than one thread. In this case the use of a synchronization object serves no purpose.

    Had each thread accessed the same global variable then synchronization objects would have been needed.

    The next question to ask yourself is whether or not using a semaphore in this circumstance is the best choice.  What you want to ensure here is that the access to a global variable is mutually exclusive between threads.  So using a mutex makes more sense. Semaphores are intended to be used where some number of threads (possibly more than 1) can be allowed to simultaneously access a shared resource.

    Although using a semaphore whose maximum count is 1 may provide the same thread-safety as using a mutex, using a mutex makes explicit the mutually exclusive nature of the desired access and prevents errors that may result if the maximum count for the semaphore is set to a number greater than 1.

    Monday, September 9, 2019 2:44 PM
  • Where does that code execute HeapAlloc? I do not see that.

    In C++ class instances can be allocated using new. That should be the preferred way to execute memory unless there is a reason it cannot be used.



    Sam Hobbs
    SimpleSamples.Info

    Monday, September 9, 2019 5:34 PM
  • Allow me to suggest that you start using std::thread for your threading.  Not only is it much easier to use than the Microsoft-specific beginthread calls, but they will work on any standard-compliant C++ compiler, including Mac and Linux.

    #include <Windows.h>
    #include <iostream>
    #include <thread>
    
    using namespace std;
    
    HANDLE semaphore;
    int count1 = 0;
    int count2 = 0;
    int count3 = 0;
    
    void addCount(int increment)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count1 += increment;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void subCount(int decrement)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count2 -= decrement;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    void mulCount(int multiply)
    {
        WaitForSingleObject(semaphore, INFINITE);
        count3 *= multiply;
        ReleaseSemaphore(semaphore, 1, 0);
    }
    
    int main(int argc, char* argv[])
    {
        HANDLE myhandleA, myhandleB, myhandleC;
    
        semaphore = CreateSemaphore(0, 1, 1, 0);
        std::thread a( &addCount, 12 );
        std::thread b( &subCount, 56 );
        std::thread c( &mulCount, 22 );
    
        a.join();
        b.join();
        c.join();
    
        cout << "Count1 = " << count1 << endl;
        cout << "Count2 = " << count2 << endl;
        cout << "Count3 = " << count3 << endl;
    
        CloseHandle(semaphore);
    
        return 0;
    }
    


    Tim Roberts | Driver MVP Emeritus | Providenza &amp; Boekelheide, Inc.

    Monday, September 9, 2019 5:55 PM
  • A slightly modified sample code using semaphores below.

    https://www.bogotobogo.com/cplusplus/multithreading_win32B.php

    I think you are getting many reasons to not use that sample code.



    Sam Hobbs
    SimpleSamples.Info

    Monday, September 9, 2019 6:39 PM
  • Sorry, but this application is an exercise, and must be realized using certain WinAPI functions. There is also no HeapAlloc() at the moment, it's only one of the guidelines including also, CreateThread(), WaitForMultipleObject(), semaphores and critical section.
    Although I did not want to initially put all the code because it is long, not finished and rather not very good :), I will do it because I also have a problem with passing parameters to functions in separate threads. This is the second application process, the first generates random numbers in the range 1-100 and saves them to the file "random.txt". The second is to read file, calculate the minimum, maximum and average in three independent threads and measure time using the QueryPerformanceFrequency() function. Initially, I would like to perform synchronization using a semaphore and later a critical section.
    At this point, however, I have a problem with the "vecIntNumbers" function parameter. Passing it via a global variable is probably not a good idea, will a structure be needed or can it be done in a simpler way?
    #include <iostream>
    #include <windows.h>
    #include <tchar.h>
    #include <stdio.h>
    #include <string>
    #include <vector>
    #include <sstream>
    #include <algorithm>
    #include <process.h>
    
    using namespace std;
    
    string GetLastErrorStdStr();
    double GetAvg(vector<int> vecIntNumbers);
    int GetMin(vector<int> vecIntNumbers);
    int GetMax(vector<int> vecIntNumbers);
    unsigned int __stdcall mythread1(void*);
    unsigned int __stdcall mythread2(void*);
    unsigned int __stdcall mythread3(void*);
    
    int main(int argc, char *argv[])
    {
        HANDLE hFile;
        DWORD dwBytesRead = 0;
    
        //Spinlock
        do {
            cout << "Spinlock...\n";
            hFile=CreateFile(TEXT("random.txt"),
                             GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        }
        while(hFile == INVALID_HANDLE_VALUE);
    
        DWORD fileSize = GetFileSize(hFile, NULL);
        int bufferSize = fileSize/sizeof(char);
        char readBuffer[bufferSize-1];
    
        if(FALSE == ReadFile(hFile, &readBuffer[0], fileSize, &dwBytesRead, NULL)) {
            cout << "\n\nSystem Error Code " << "(" << GetLastError() << "): " << GetLastErrorStdStr();
            CloseHandle(hFile);
            return 1;
        }
        else
            cout << "\nOpen for reading!" << endl;
    
        if (dwBytesRead > 0) {
            // NULL character
            readBuffer[dwBytesRead+1] = '\0';
            cout << "Read " << dwBytesRead << " bytes from: random.txt" << endl;
            cout << "\n" << readBuffer << endl;
        }
        else {
            cout << "No data read from file!" << endl;
        }
    
        CloseHandle(hFile);
    
        istringstream ss(readBuffer);
        string numbers;
        vector<string> vecStrNumbers;
    
        while(getline(ss, numbers, ' ')) {
            vecStrNumbers.push_back(numbers);
        }
    
        cout << endl;
    
        vector<int> vecIntNumbers;
        int size_v = vecStrNumbers.size();
        for(int i=0; i<size_v; i++) {
            vecIntNumbers.push_back(atoi((vecStrNumbers[i]).c_str()));
            //cout << vecIntNumbers.at(i) << " ";
        }
    
        HANDLE myhandleA, myhandleB, myhandleC;
    
        myhandleA = (HANDLE)_beginthreadex(0, 0, &mythread1, (void*)0, 0, 0);
        myhandleB = (HANDLE)_beginthreadex(0, 0, &mythread2, (void*)0, 0, 0);
        myhandleC = (HANDLE)_beginthreadex(0, 0, &mythread3, (void*)0, 0, 0);
    
        WaitForSingleObject(myhandleA, INFINITE);
        WaitForSingleObject(myhandleB, INFINITE);
        WaitForSingleObject(myhandleC, INFINITE);
    
        CloseHandle(myhandleA);
        CloseHandle(myhandleB);
        CloseHandle(myhandleC);
    
        // Used before for calculation output:
        //cout << "Average: " << GetAvg(vecIntNumbers) << endl;
        //cout << "Minimum: " << GetMin(vecIntNumbers) << endl;
        //cout << "Maximum: " << GetMax(vecIntNumbers) << endl;
    
        return 0;
    }
    
    string GetLastErrorStdStr()
    {
        DWORD error = GetLastError();
        if (error) {
            LPVOID lpMsgBuf;
            DWORD bufLen = FormatMessage(
                               FORMAT_MESSAGE_ALLOCATE_BUFFER |
                               FORMAT_MESSAGE_FROM_SYSTEM |
                               FORMAT_MESSAGE_IGNORE_INSERTS,
                               NULL,
                               error,
                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                               (LPTSTR) &lpMsgBuf,
                               0, NULL );
            if (bufLen) {
                LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf;
                string result(lpMsgStr, lpMsgStr+bufLen);
    
                LocalFree(lpMsgBuf);
    
                return result;
            }
        }
        return string();
    }
    
    double GetAvg(vector<int> vecIntNumbers)
    {
        double sum = 0;
        for(int i=0; i<(int)vecIntNumbers.size(); ++i) {
            sum += vecIntNumbers[i];
        }
        return sum/vecIntNumbers.size();
    }
    
    int GetMin(vector<int> vecIntNumbers)
    {
        sort(vecIntNumbers.begin(), vecIntNumbers.end());
        return vecIntNumbers.front();
    }
    
    int GetMax(vector<int> vecIntNumbers)
    {
        sort(vecIntNumbers.begin(), vecIntNumbers.end());
        return vecIntNumbers.back();
    }
    
    unsigned int __stdcall mythread1(void*)
    {
        GetAvg(vecIntNumbers);
        return 0;
    }
    
    unsigned int __stdcall mythread2(void*)
    {
        GetMin(vecIntNumbers);
        return 0;
    }
    
    unsigned int __stdcall mythread3(void*)
    {
        GetMax(vecIntNumbers);
        return 0;
    }
    Monday, September 9, 2019 7:04 PM
  • At this point, however, I have a problem with the "vecIntNumbers" function parameter. Passing it via a global variable is probably not a good idea, will a structure be needed or can it be done in a simpler way?
    You can pass a pointer to the vector to each thread.
    Monday, September 9, 2019 9:13 PM
  • You can't run those threads in parallel as you have written them, because mythread2 and mythread3 modify the vector.  The two threads will interfere with each other.

    You'll either need to use std::max_element and std::min_element, or do it with a loop.  Just make sure you don't change the vector.

    Once you do that, no synchronization is necessary, because none of the threads are changing any shared state.  However, there are other big problems here.  GetAvg, GetMin, and GetMax all returns their answers, but your threads promptly throw that data away.  Remember that you can pass a piece of data to your threads.  You are currently passing (void*)0.  One possible answer is to create a structure where you store a pointer to the vector and a spot for the answer, and then pass that to the thread.


    Tim Roberts | Driver MVP Emeritus | Providenza &amp; Boekelheide, Inc.

    Tuesday, September 10, 2019 6:22 AM
  • And these functions -

    double GetAvg(vector<int> vecIntNumbers);
    int GetMin(vector<int> vecIntNumbers);
    int GetMax(vector<int> vecIntNumbers);
    

    all result in passing a copy of the vector.  So they are thread-safe by accident.

    Another possibility is to pass the vector by reference to avoid the copy and as Tim Roberts has suggested use min_element and max_element instead of sort to avoid modifying the vector.

    Tuesday, September 10, 2019 9:52 AM
  • Thank you all for the suggestions, I rewrote some of the code again using the appropriate example from the MSDN library below. Everything seems to work as it should, also the critical section I added, which as assumed in the exercise content is to synchronize the display of the results of three threads in the console.
    But now I have another problem, is it possible to pass three different functions from outside the main function to three threads created in a loop? The argument is only one "vecIntNumbers" and I pass it through the structure, I just split the loop into two parts. Can I do it with an array?
    Another thing is that I would also like to control the order in which the threads are called in the loop and measure their execution time later using
    QueryPerformanceFrequency().

    https://docs.microsoft.com/en-us/windows/win32/procthread/creating-threads

    #include <iostream>
    (...)
    #define MAX_THREADS 3
    
    using namespace std;
    
    string GetLastErrorStdStr();
    double GetAvg(vector<int> vecIntNumbers);
    int GetMin(vector<int> vecIntNumbers);
    int GetMax(vector<int> vecIntNumbers);
    
    DWORD WINAPI AvgThreadFunction( LPVOID lpParam );
    DWORD WINAPI MinThreadFunction( LPVOID lpParam );
    DWORD WINAPI MaxThreadFunction( LPVOID lpParam );
    
    typedef struct MyData {
        vector<int> vecIntNumbers;
    } MYDATA, *PMYDATA;
    
    int main(int argc, char *argv[])
    {
        HANDLE hFile;
        DWORD dwBytesRead = 0;
        PMYDATA pDataArray[MAX_THREADS];
        DWORD   dwThreadIdArray[MAX_THREADS];
        HANDLE  hThreadArray[MAX_THREADS];
    
       (...)
    
        for( int i=0; i<MAX_THREADS; i++ ) {
            // Allocate memory for thread data.
            pDataArray[i] = (PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MYDATA));
    
            if( pDataArray[i] == NULL ) {
                ExitProcess(2);
            }
    
            // Generate unique data for each thread to work with.
            pDataArray[i]->vecIntNumbers = vecIntNumbers;
        }
            // Create the thread to begin execution on its own.
            hThreadArray[0] = CreateThread(NULL, 0, AvgThreadFunction, pDataArray[0], 0, &dwThreadIdArray[0]);
            hThreadArray[1] = CreateThread(NULL, 0, MinThreadFunction, pDataArray[1], 0, &dwThreadIdArray[1]);
            hThreadArray[2] = CreateThread(NULL, 0, MaxThreadFunction, pDataArray[2], 0, &dwThreadIdArray[2]);
    
            // Check the return value for success.
            // If CreateThread fails, terminate execution.
            // This will automatically clean up threads and memory.
        for( int i=0; i<MAX_THREADS; i++ ) {
            if (hThreadArray[i] == NULL) {
                cout << "\n\nSystem Error Code " << "(" << GetLastError() << "): " << GetLastErrorStdStr();
                ExitProcess(3);
            }
        } // End of main thread creation loop.
    
        // Wait until all threads have terminated.
        WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);
    
        // Close all thread handles and free memory allocations.
        for(int i=0; i<MAX_THREADS; i++) {
            CloseHandle(hThreadArray[i]);
            if(pDataArray[i] != NULL) {
                HeapFree(GetProcessHeap(), 0, pDataArray[i]);
                pDataArray[i] = NULL;    // Ensure address is not reused.
            }
        }
        return 0;
    }
    
    string GetLastErrorStdStr()
    {
        (...)
        return string();
    }
    
    double GetAvg(vector<int> vecIntNumbers)
    {
        double sum = 0;
        for(int i=0; i<(int)vecIntNumbers.size(); ++i) {
            sum += vecIntNumbers[i];
        }
        return sum/vecIntNumbers.size();
    }
    
    int GetMin(vector<int> vecIntNumbers)
    {
        int minimum = *min_element(vecIntNumbers.begin(), vecIntNumbers.end());
        return minimum;
    }
    
    int GetMax(vector<int> vecIntNumbers)
    {
        int maximum = *max_element(vecIntNumbers.begin(), vecIntNumbers.end());
        return maximum;
    }
    
    DWORD WINAPI AvgThreadFunction( LPVOID lpParam )
    {
        PMYDATA pDataArray;
        pDataArray = (PMYDATA)lpParam;
    
        vector<int> vecIntNumbers = pDataArray->vecIntNumbers;
        cout << "Average: " << GetAvg(vecIntNumbers) << endl;
    
        return 0;
    }
    
    DWORD WINAPI MinThreadFunction( LPVOID lpParam )
    {
        PMYDATA pDataArray;
        pDataArray = (PMYDATA)lpParam;
    
        vector<int> vecIntNumbers = pDataArray->vecIntNumbers;
        cout << "Min: " << GetMin(vecIntNumbers) << endl;
    
        return 0;
    }
    
    DWORD WINAPI MaxThreadFunction( LPVOID lpParam )
    {
        PMYDATA pDataArray;
        pDataArray = (PMYDATA)lpParam;
    
        vector<int> vecIntNumbers = pDataArray->vecIntNumbers;
        cout << "Max: " << GetMax(vecIntNumbers) << endl;
    
        return 0;
    }
    

    Wednesday, September 11, 2019 5:27 PM
  • A quick observation -

            pDataArray[i] = (PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MYDATA));
    

    HeapAlloc will create a pointer to a structure that contains an uninitialized vector object.  HeapAlloc, unlike memory allocations made by new, will not call the constructors of C++ objects.

    If you would like to clearly see the effect of this problem, remove the HEAP_ZERO_MEMORY parameter from the call to HeapAlloc.

    Wednesday, September 11, 2019 10:49 PM
  • As a follow up for what RLWA stated, if you wanted to use HeapAlloc for class types then you would need to call placement new on each element. When you want to free memory, you would need to call the destructor manually before you call HeapFree.

    The new[] and delete[] operators do this automatically, but if you don't use them you must use placement new and the destructor to call the constructor and destructor since as stated the compiler won't do it automatically.

    As an example:

    #include <Windows.h>
    #include <vector>
    #include <new>
    
    struct my_struct
    {
    	std::vector<int> v;
    };
    
    constexpr size_t elems = 500;
    
    int wmain()
    {
    	//single item allocation
    	my_struct *struct_array = reinterpret_cast<my_struct*>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(my_struct)));
    
    	new(struct_array) my_struct{}; //placement new, calls my_struct::my_struct which must be called because of vector
    
    	struct_array->~my_struct(); //call the destructor to free up memory, must be used because of vector
    
    	HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, struct_array);
    	
    	struct_array = nullptr;
    
    	//array allocation
    	struct_array = reinterpret_cast<my_struct*>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(my_struct) * elems));
    
    	for (size_t i = 0; i < elems; ++i)
    	{
    		//placement new, called for every element in the array
    		new(&struct_array[i]) my_struct{};
    	}
    
    	for (size_t i = 0; i < elems; ++i)
    	{
    		//call the destructor, called for every element in the array
    		struct_array[i].~my_struct();
    	}
    
    	HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, struct_array);
    
    	struct_array = nullptr;
    
    	return 0;
    }

    Also this is required if you want to use vector. The vector constructor does initialisation in the constructor and if you skip calling the constructor then the program will most likely crash when you use it. The destructor must also be called because vector allocates memory. The only way to get vector to free the allocated memory is by calling the destructor.

    The only way you could ever get away with not using placement new is if you are allocating memory for a POD type and it doesn't have any initialisers for the member variables.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, September 11, 2019 11:56 PM
    Wednesday, September 11, 2019 11:54 PM
  • I am quite sure you don't understand how many copies of your vecIntNumbers you are creating.  Each element of the MYDATA array will have its own private copy of the vector.  Then, each of the three thread functions creates another private copy of that copy.  Then, when you call GetMin, GetMax and GetAvg, each call will create ANOTHER private copy of the array.  You need to use & to pass references to the original array.

    And heed RLWA32's advice.  If you are allocating C++ objects, then you must use "new", not things like HeapAlloc or VirtualAlloc.  Don't try to tell me "you must use Windows APIs".  If you're allowed to use std::vector, then you have to use functions that work with std::vector.  HeapAlloc doesn't do that.

        for( int i=0; i<MAX_THREADS; i++ ) {
            // Allocate memory for thread data.
            pDataArray[i] = new MYDATA; 
            // Generate unique data for each thread to work with.
            pDataArray[i]->vecIntNumbers = vecIntNumbers;
        }

    and

    int GetMin(const vector<int> & vecIntNumbers)
    {
        return *min_element(vecIntNumbers.begin(), vecIntNumbers.end());
    }
    

    and

    DWORD WINAPI MinThreadFunction( LPVOID lpParam )
    {
        PMYDATA pDataArray = (PMYDATA)lpParam;
        cout << "Min: " << GetMin(pDataArray->vecIntNumbers) << endl;
        return 0;
    }

    Your comment about "ordering" the threads makes me think you don't really understand what threads are for.  If you are calling functions in sequential order, then it is utterly silly to make them threads at all.  Just call the functions.  The whole POINT of calling threads is that they can all run at the same time.  The ordering doesn't matter.

    The easy way to measure the performance of the function is to do it in the thread function.  Otherwise, it's hard to match up the ending time of the thread.  If the thread ends while your main program is busy with something else, you won't get a good measure of the time.  And remember that you want QueryPerformanceCounter, not QueryPerformanceFrequency.


    Tim Roberts | Driver MVP Emeritus | Providenza &amp; Boekelheide, Inc.

    Thursday, September 12, 2019 3:50 AM