Multi threading on Win32, MFC

drjackool 956 Reputation points
2023-06-01T21:21:09.9766667+00:00

Hi

In my app (Win32/MFC platform) exist several worker threads and I want according following sample code, function A just called once at starting the fist thread and B called for each thread and C just called by last standing thread to finish the job.

Is my code correct or has some crashes?

Thanks very much!

// I do not write extra codes

LONG g_lThreadsCount = 0;

void Start()
{
	// create first worker thread
	AfxBeginThread(WorkerThreadProc);
}

void A()
{

	// this function MUST called before all other threads just once
	// preparing jobs
}

void B()
{
	// limit threads count to 8
	if (g_lThreadsCount < 8)
		AfxBeginThread(WorkerThreadProc);

	// doing jobs
}

void C()
{
	// finishing jobs
	// this function MUST called after all other threads just once
}

UINT WorkerThreadProc(LPVOID pParam)
{	
	ASSERT(pParam != NULL);	

	InterlockedIncrement(&g_lThreadsCount);

	// if this is fisrt thread, do A just once!
	if (g_lThreadsCount == 1)
	{
		A();
	}
	
	B();

	// if this is last thread, do C at ending
	if (g_lThreadsCount == 1)
	{
		C();
	}

	InterlockedDecrement(&g_lThreadsCount);

	return 0;
}
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,968 questions
{count} votes

3 answers

Sort by: Most helpful
  1. Juan Humberto Artero Jaimez 1 Reputation point
    2023-06-02T23:38:15.24+00:00
    To achieve the desired behavior in your Win32/MFC application where function A is called once at the start of the first thread, function B is called for each thread, and function C is called by the last remaining thread to finish the job, you can use synchronization primitives such as mutex and condition variables. Here's an example implementation:
    
    cpp
    Copy code
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    
    std::mutex mtx;  // Mutex for synchronization
    std::condition_variable cv;  // Condition variable for synchronization
    bool isFirstThread = true;  // Flag to indicate the first thread
    int threadCount = 0;  // Counter for active threads
    
    void FunctionA()
    {
        std::cout << "Function A called once." << std::endl;
    }
    
    void FunctionB()
    {
        std::unique_lock<std::mutex> lock(mtx);
    
        // Increment the thread count
        threadCount++;
    
        if (isFirstThread)
        {
            // Call FunctionA for the first thread
            FunctionA();
            isFirstThread = false;
        }
    
        // Call FunctionB for each thread
        std::cout << "Function B called by thread." << std::this_thread::get_id() << std::endl;
    
        // Wait until the last thread finishes
        cv.wait(lock, [] { return threadCount == 0; });
    
        // Call FunctionC for the last thread
        std::cout << "Function C called by the last thread." << std::endl;
    }
    
    int main()
    {
        const int NUM_THREADS = 5;
    
        // Create worker threads
        std::vector<std::thread> threads;
        for (int i = 0; i < NUM_THREADS; i++)
        {
            threads.emplace_back(FunctionB);
        }
    
        // Wait for threads to finish
        for (auto& thread : threads)
        {
            thread.join();
        }
    
        return 0;
    }
    In this example, we use a mutex (mtx) and a condition variable (cv) to synchronize the threads. The isFirstThread flag is initially set to true, and the threadCount counter keeps track of the active threads.
    
    Each worker thread executes FunctionB(), where it first locks the mutex and increments the thread count. If it's the first thread (isFirstThread is true), it calls FunctionA() and sets isFirstThread to false. Then, it calls FunctionB() for each thread and prints a message indicating the thread ID.
    
    Afterward, the thread waits on the condition variable cv until the threadCount reaches zero. This means that all other threads have finished executing FunctionB(). The lambda function passed to cv.wait() checks if threadCount is zero. When the last thread calls cv.notify_all() (outside the lambda function), all threads waiting on the condition variable are awakened. The last thread proceeds to call FunctionC() and prints a message.
    
    In the main() function, we create NUM_THREADS worker threads and wait for them to finish using join().
    
    Make sure to include the necessary headers (<iostream>, <thread>, <mutex>, <condition_variable>) and link against the appropriate libraries (kernel32.lib, user32.lib, gdi32.lib) for Win32/MFC applications.
    

  2. drjackool 956 Reputation points
    2023-06-03T01:32:36.3833333+00:00

    How about this code?!

    I invented another way 😎 and think this is solid and full safe! without polling CPU

    LONG g_lThreadsRunning = 0;
    LONG g_lThreadsDone = 0;
    LONG g_lThreadsFailed = 0;
    
    #define MAX_THREADS     8
    
    void Start()
    {
    	// create first worker thread
    	AfxBeginThread(WorkerThreadProc);
    }
    
    void A()
    {
    	// preparing jobs
    }
    
    void B()
    {
    	// I won't trying to recreate failed thread, so add faileds to runnings
    	if (g_lThreadsRunning + g_lThreadsFailed < MAX_THREADS)
    	{
    		if (AfxBeginThread(WorkerThreadProc) == NULL) // also should check resume result
    			InterlockedIncrement(&g_lThreadsFailed);
    	}
    
    	// doing jobs
    }
    
    void C()
    {
    	// finishing jobs
    }
    
    UINT WorkerThreadProc(LPVOID pParam)
    {	
    	// if this is first thread, do A just once!
    	if (InterlockedIncrement(&g_lThreadsRunning) == 1)
    	{
    		A();
    	}
    	
    	B();
    
    	// if this is last thread, do C at ending
    	if (g_lThreadsDone + g_lThreadsFailed + 1 == MAX_THREADS)
    	{
    		C();
    	}
    
    	InterlockedIncrement(&g_lThreadsDone);
    
    	return 0;
    }
    

  3. RLWA32 49,316 Reputation points
    2023-06-03T08:51:21.5366667+00:00

    An alternative to using global counters to manage threads --

    Assuming that the process using the MFC worker threads is a GUI process.

    In the main process get the work started

    AfxBeginThread(ThreadController, m_hWnd);
    

    Worker details-

    // Post this to the GUI thread when finished
    #define WM_THREADSFINISHED WM_APP + 42
    
    void A()
    {
        TRACE(_T("In %s, tid %d\n"), _T(__FUNCTION__), GetCurrentThreadId());
    }
    
    UINT  B(LPVOID pv)
    {
        auto tid = GetCurrentThreadId();
        TRACE(_T("In %s, tid %d\n"), _T(__FUNCTION__), tid);
        Sleep(RandomWait()); // Simulate doing work
        TRACE(_T("%s on tid %d Finished\n"), _T(__FUNCTION__), tid);
        return 0;
    }
    
    void C()
    {
        TRACE(_T("In %s, tid %d\n"), _T(__FUNCTION__), GetCurrentThreadId());
    }
    
    
    UINT ThreadController(LPVOID pv)
    {
        HWND hDlg = reinterpret_cast<HWND>(pv);
        const int THREADS{ 8 };
        HANDLE hBworkers[THREADS]{};
    
        TRACE(_T("In %s, thread %d\n"), _T(__FUNCTION__), GetCurrentThreadId());
    
        A();
    
        // Create threads suspended to relieably retrieve handles
        for (int i = 0; i < THREADS; i++)
        {
            auto pcwt = AfxBeginThread(B, NULL, 0, 0, CREATE_SUSPENDED);
            ASSERT_VALID(pcwt);
            ::DuplicateHandle(GetCurrentProcess(), pcwt->m_hThread, GetCurrentProcess(), &hBworkers[i], 0, FALSE, DUPLICATE_SAME_ACCESS);
            ASSERT(hBworkers[i] != NULL);
            pcwt->ResumeThread();
        }
    
        auto result = ::WaitForMultipleObjects(THREADS, hBworkers, TRUE, INFINITE);
        ASSERT(result >= WAIT_OBJECT_0 && result <= (WAIT_OBJECT_0 + THREADS - 1));
    
        // All threads are finished
        // Optional loop through HANDLE array and check thread return codes with GetExitCodeThread
    
        C();
    
        // Close thread handles
        for (int i = 0; i < THREADS; i++)
            CloseHandle(hBworkers[i]);
    
        // Let UI thread know all work is done
        ::PostMessage(hDlg, WM_THREADSFINISHED, 0, 0);
    
        return 0;
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.