Easy Tutorial
❮ Android Tutorial Canvas Api3 Hex Dec Oct Bin ❯

C++ Multithreading

Category Programming Technology

Creating Threads

On the Windows platform, the Windows API provides support for multithreading. As mentioned in the concepts of processes and threads, a program must have at least one thread, which is called the main thread. If we do not explicitly create a thread, our program will only have the main thread.

Below, let's look at the operations and methods related to threads in Windows:

CreateThread and CloseHandle

CreateThread is used to create a thread, with the following function prototype:

HANDLE WINAPI CreateThread(
    LPSECURITY_ATTRIBUTES   lpThreadAttributes, // Security attributes related to the thread, often set to NULL
    SIZE_T                  dwStackSize,        // Initial stack size for the new thread, can be set to 0
    LPTHREAD_START_ROUTINE  lpStartAddress,     // Callback function to be executed by the thread, also known as the thread function
    LPVOID                  lpParameter,        // Parameter passed to the thread function, NULL if no parameter is needed
    DWORD                   dwCreationFlags,    // Flags controlling the thread creation
    LPDWORD                 lpThreadId          // Output parameter, used to get the thread ID, NULL if the thread ID is not needed
);

Explanation:

[Demo1]: Creating a Simple Thread

Example

#include "stdafx.h"
#include <windows.h>
#include <iostream>

using namespace std;

// Thread function
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    for (int i = 0; i < 5; ++i)
    {
        cout << "Child Thread: i = " << i << endl;
        Sleep(100);
    }
    return 0L;
}

int main()
{
    // Create a thread
    HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    // Close the thread
    CloseHandle(thread);

    // Main thread execution path
    for (int i = 0; i < 5; ++i)
    {
        cout << "Main Thread: i = " << i << endl;
        Sleep(100);
    }

    return 0;
}

Result:

Main Thread: i = 0
Child Thread: i = 0
Main Thread: i = 1
Child Thread: i = 1
Child Thread: i = 2
Main Thread: i = 2
Child Thread: i = 3
Main Thread: i = 3
Child Thread: i = 4
Main Thread: i = 4

[Demo2]: Passing Parameters in the Thread Function

Example

#include "stdafx.h"
#include <windows.h>
#include <iostream>

using namespace std;

#define NAME_LINE   40

// Define the structure for thread function parameters
typedef struct __THREAD_DATA
{
    int nMaxNum;
    char strThreadName[NAME_LINE];

    __THREAD_DATA() : nMaxNum(0)
    {
        memset(strThreadName, 0, NAME_LINE * sizeof(char));
    }
} THREAD_DATA;

// Thread function
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;

    for (int i = 0; i < pThreadData->nMaxNum; ++i)
    {
        cout << pThreadData->strThreadName << " --- " << i << endl;
        Sleep(100);
    }
    return 0L;
}

int main()
{
    // Initialize thread data
    THREAD_DATA threadData1, threadData2;
    threadData1.nMaxNum = 5;
    strcpy(threadData1.strThreadName, "Thread 1");
    threadData2.nMaxNum = 10;
    strcpy(threadData2.strThreadName, "Thread 2");

    // Create the first child thread
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
    // Create the second child thread
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
    // Close the threads
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    // Main thread execution path
    for (int i = 0; i < 5; ++i)
    {
        cout << "Main Thread === " << i << endl;
        Sleep(100);
    }

    system("pause");
    return 0;
}

Result:

Main Thread === Thread 1 — 0
0
Thread 2 — 0
Thread 1 — 1
Main Thread === 1
Thread 2 — 1
Main Thread === 2
Thread 1 — 2
Thread 2 — 2
Main Thread === 3
Thread 2 — 3
Thread 1 — 3
Main Thread === 4
Thread 2 — 4
Thread 1 — 4
Thread 2 — 5
Press any key to continue… Thread 2 — 6
Thread 2 — 7
Thread 2 — 8
Thread 2 — 9

CreateMutex, WaitForSingleObject, ReleaseMutex

From [Demo2], we can see that although the created child threads are running normally, the output is not what we expected. We expected each statement to be output followed by a newline, but the result is not always like this. This is because no synchronization is done when the threads are executed, for example, the first line of output. The main thread outputs "Main Thread ===" and then the time slice is used up. Then it's the child thread 1's turn to output, and after outputting "Thread 1 —", the time slice is also used up. Then it's the main thread's turn to execute and output "0", and then it's the child thread 1's turn to output "0". Thus, the result "Main Thread === Thread 1 — 0 0" appears.

Main thread: cout << "Main Thread === " << i << endl;

To avoid this situation, we do some simple synchronization for the threads, using a Mutex here.

A Mutex is similar to a binary semaphore, allowing only one thread to access the resource. Unlike a binary semaphore, a semaphore can be acquired and released by any thread in the system, meaning the same semaphore can be acquired by one thread and released by another. A Mutex, however, requires the thread that acquires the Mutex lock to be the one that releases it; other threads releasing the Mutex are ineffective.

The following functions are used when using a Mutex for thread synchronization:

HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        // Security attributes related to the thread, often set to NULL
    BOOL                  bInitialOwner,            // Whether the creating thread owns the Mutex initially
    LPCTSTR               lpName                    // Name of the Mutex
);

Explanation: lpMutexAttributes also indicates security, similar to lpThreadAttributes in CreateThread, determining whether the returned handle can be inherited by child processes. If NULL, the handle cannot be inherited. bInitialOwner indicates whether the creating thread owns the Mutex initially. If TRUE, the creating thread is designated as the owner of the Mutex object, and other threads need to release the Mutex first before accessing it. lpName is the name of the Mutex.

DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                             // Handle of the lock to acquire
    DWORD  dwMilliseconds                           // Timeout interval
);

Explanation: WaitForSingleObject waits for a specified object (such as a Mutex object) until that object is no longer occupied (such as the Mutex being released) or the timeout interval is exceeded. Additionally, there is a similar function, WaitForMultipleObjects, which waits for one or all specified objects until all are no longer occupied or the timeout interval is exceeded.

hHandle: Handle of the object to wait for. dwMilliseconds: Timeout interval in milliseconds; if dwMilliseconds is non-zero, it waits until the timeout interval is exceeded or the object is no longer occupied. If dwMilliseconds is INFINITE, it waits indefinitely until the object is no longer occupied.

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

Explanation: Release the mutex lock object that you possess, where hMutex is the handle of the mutex to be released.

[Demo3]: Thread Synchronization

Example

#include "stdafx.h"
#include <windows.h>
#include <iostream>

#define NAME_LINE   40

// Define the structure for thread function parameters
typedef struct __THREAD_DATA
{
    int nMaxNum;
    char strThreadName[NAME_LINE];

    __THREAD_DATA() : nMaxNum(0)
    {
        memset(strThreadName, 0, NAME_LINE * sizeof(char));
    }
} THREAD_DATA;

HANDLE g_hMutex = NULL;     // Mutex

// Thread function
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;

    for (int i = 0; i < pThreadData->nMaxNum; ++i)
    {
        // Request a mutex lock
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << pThreadData->strThreadName << " --- " << i << endl;
        Sleep(100);
        // Release the mutex lock
        ReleaseMutex(g_hMutex);
    }
    return 0L;
}

int main()
{
    // Create a mutex
    g_hMutex = CreateMutex(NULL, FALSE, NULL);

    // Initialize thread data
    THREAD_DATA threadData1, threadData2;
    threadData1.nMaxNum = 5;
    strcpy(threadData1.strThreadName, "Thread1");
    threadData2.nMaxNum = 10;
    strcpy(threadData2.strThreadName, "Thread2");

    // Create the first child thread
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
    // Create the second child thread
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
    // Close the threads
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    // Main thread execution path
    for (int i = 0; i < 5; ++i)
    {
        // Request a mutex lock
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << "Main Thread === " << i << endl;
        Sleep(100);
        // Release the mutex lock
        ReleaseMutex(g_hMutex);
    }

    system("pause");
    return 0;
}

Result:

Main Thread === 0 
Thread1 — 0 
Thread2 — 0 
Main Thread === 1 
Thread1 — 1 
Thread2 — 1 
Main Thread === 2 
Thread1 — 2 
Thread2 — 2 
Main Thread === 3 
Thread1 — 3 
Thread2 — 3 
Main Thread === 4 
Thread1 — 4 
Press any key to continue… Thread2 — 4 
Thread2 — 5 
Thread2 — 6 
Thread2 — 7 
Thread2 — 8 
Thread2 — 9

To further understand the importance of thread synchronization and the use of mutexes, let's look at another example.

Buying train tickets is a major concern for people returning home during the Spring Festival. We will simulate a simple train ticket sales system (for simplicity, we will extract the simplest model for simulation): There are 500 train tickets from Beijing to Ganzhou, sold simultaneously at 8 windows, ensuring system stability and data atomicity.

[Demo4]: Simulating Train Ticket Sales System

SaleTickets.h

#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <strstream> 
#include <string>

using namespace std;

#define NAME_LINE   40

// Define the structure for thread function parameters
typedef struct __TICKET
{
    int nCount;
    char strTicketName[NAME_LINE];

    __TICKET() : nCount(0)
    {
        memset(strTicketName, 0, NAME_LINE * sizeof(char));
    }
} TICKET;

typedef struct __THD_DATA
{
    TICKET* pTicket;
    char strThreadName[NAME_LINE];

    __THD_DATA() : pTicket(NULL)
    {
        memset(strThreadName, 0, NAME_LINE * sizeof(char));
    }
} THD_DATA;

// Convert basic type data to string
template&lt;class T>
string convertToString(const T val)
{
    string s;
    std::strstream ss;
    ss << val;
    ss >> s;
    return s;
}

// Ticket selling program
DWORD WINAPI SaleTicket(LPVOID lpParameter);

SaleTickets.cpp

#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include "SaleTickets.h"

using namespace std;

extern HANDLE g_hMutex;

// Ticket selling program
DWORD WINAPI SaleTicket(LPVOID lpParameter)
{
    THD_DATA* pThreadData = (THD_DATA*)lpParameter;
    TICKET* pSaleData = pThreadData->pTicket;
    while (pSaleData->nCount > 0)
    {
        // Request a mutex lock
        WaitForSingleObject(g_hMutex, INFINITE);
        if (pSaleData->nCount > 0)
        {
            cout << pThreadData->strThreadName << " sold ticket " << pSaleData->nCount-- << ", ";
            if (pSaleData->nCount >= 0) {
                cout << "Ticket sold successfully! Remaining " << pSaleData->nCount << " tickets." << endl;
            } else {
                cout << "Ticket sale failed! Tickets sold out." << endl;
            }
        }
        Sleep(10);
        // Release the mutex lock
        ReleaseMutex(g_hMutex);
    }

    return 0L;
}

Test program:

// Ticket selling system
void Test2()
{
    // Create a mutex
    g_hMutex = CreateMutex(NULL, FALSE, NULL);

    // Initialize train tickets
    TICKET ticket;
    ticket.nCount = 100;
    strcpy(ticket.strTicketName, "Beijing-->Ganzhou");

    const int THREAD_NUMM = 8;
    THD_DATA threadSale[THREAD_NUMM];
    HANDLE hThread[THREAD_NUMM];
    for (int i = 0; i < THREAD_NUMM; ++i)
    {
        threadSale[i].pTicket = &ticket;
        string strThreadName = convertToString(i);

        strThreadName = "Window" + strThreadName;

        strcpy(threadSale[i].strThreadName, strThreadName.c_str());

        // Create thread
        hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);

        // Request a mutex lock
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << threadSale[i].strThreadName << " started selling " << threadSale[i].pTicket->strTicketName << " tickets..." << endl;
        // Release the mutex lock
        ReleaseMutex(g_hMutex);

        // Close the thread
        CloseHandle(hThread[i]);
    }

    system("pause");
}

Result:

Window0 started selling Beijing–>Ganzhou tickets… 
Window0 sold ticket 100, Ticket sold successfully! Remaining 99 tickets. 
Window1 started selling Beijing–>Ganzhou tickets… 
Window1 sold ticket 99, Ticket sold successfully! Remaining 98 tickets. 
Window0 sold ticket 98, Ticket sold successfully! Remaining 97 tickets. 
Window2 started selling Beijing–>Ganzhou tickets… 
Window2 sold ticket 97, Ticket sold successfully! Remaining 96 tickets. 
Window1 sold ticket 96, Ticket sold successfully! Remaining 95 tickets. 
Window0 sold ticket 95, Ticket sold successfully! Remaining 94 tickets. 
Window3 started selling Beijing–>Ganzhou tickets… 
Window3 sold ticket 94, Ticket sold successfully! Remaining 93 tickets. 
Window2 sold ticket 93, Ticket sold successfully! Remaining 92 tickets. 
Window1 sold ticket 92, Ticket sold successfully! Remaining 91 tickets. 
Window0 sold ticket 91, Ticket sold successfully! Remaining 90 tickets. 
Window4 started selling Beijing–>Ganzhou tickets… 
Window4 sold ticket 90, Ticket sold successfully! Remaining 89 tickets. 
Window3 sold ticket 89, Ticket sold successfully! Remaining 88 tickets. 
Window2 sold ticket 88, Ticket sold successfully! Remaining 87 tickets. 
Window1 sold ticket 87, Ticket sold successfully! Remaining 86 tickets. 
Window0 sold ticket 86, Ticket sold successfully! Remaining 85 tickets. 
Window5 started selling Beijing–>Ganzhou tickets… 
Window5 sold ticket 85, Ticket sold successfully! Remaining 84 tickets. 
Window4 sold ticket 84, Ticket sold successfully! Remaining 83 tickets. 
Window3 sold ticket 83, Ticket sold successfully! Remaining 82 tickets. 
Window2 sold ticket 82, Ticket sold successfully! Remaining 81 tickets.

Source: http://blog.csdn.net/luoweifu/article/details/46835437

❮ Android Tutorial Canvas Api3 Hex Dec Oct Bin ❯