Creating Threads

On the Windows platform, the Windows API provides support for multithreading. Earlier, in the concepts of processes and threads, we mentioned that a program has at least one thread, which is called the main thread. If we do not explicitly create threads, the program we produce is a single-threaded program with only the main thread.

Below, let's look at the thread-related operations and methods in Windows:

CreateThread and CloseHandle

CreateThread is used to create a thread. Its function prototype is as follows:

HANDLE WINAPI CreateThread(
    LPSECURITY_ATTRIBUTES   lpThreadAttributes, //线程安全相关的属性,常置为NULL
    SIZE_T                  dwStackSize,        //新线程的初始化栈在大小,可设置为0
    LPTHREAD_START_ROUTINE  lpStartAddress,     //被线程执行的回调函数,也称为线程函数
    LPVOID                  lpParameter,        //传入线程函数的参数,不需传递参数时为NULL
    DWORD                   dwCreationFlags,    //控制线程创建的标志
    LPDWORD                 lpThreadId          //传出参数,用于获得线程ID,如果为NULL则不返回线程ID
);

Description:

  • lpThreadAttributes: A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If it is NULL, the returned handle cannot be inherited by child processes.

  • dwStackSize: The initial size of the thread stack, in bytes. The system allocates this value...
  • lpStartAddress: A pointer to a function pointer; this function will be called and executed by the thread. Therefore, this function is also called the thread function (ThreadProc), which is the starting address of thread execution. The thread function is a callback function called by the operating system in the thread. The prototype of the thread function is as follows:

    DWORD WINAPI ThreadProc(LPVOID lpParameter);    //lpParameter是传入的参数,是一个空指针
  • lpParameter: The parameter passed to the thread function (ThreadProc); use NULL when no parameter needs to be passed.

  • dwCreationFlags: Flags controlling thread creation. There are three types: 0: the thread executes immediately after creation; CREATE_SUSPENDED: the thread enters the ready state after creation and is not invoked until the thread is resumed; STACK_SIZE_PARAM_IS_A_RESERVATION: the dwStackSize parameter specifies the initial stack size of the thread. If the STACK_SIZE_PARAM_IS_A_RESERVATION flag is not specified, dwStackSize will be set to a system-reserved value.

  • Return value: If the thread is created successfully, the handle of the new thread is returned; otherwise, NULL is returned. If thread creation fails, error information can be obtained through the GetLastError function.

    BOOL WINAPI CloseHandle(HANDLE hObject);        //关闭一个被打开的对象句柄
    
    This function can be used to close the created thread handle. If the function executes successfully, it returns true (non-zero); if it fails, it returns false (0). If execution fails, you can call the GetLastError function to obtain error information.

[Demo1]: Create the Simplest 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 thread CloseHandle(thread); //execution path of the main thread for (int i = 0; i < 5; ++ i) { cout << "main thread: i =" << i << endl; Sleep(100); } return 0; }

The result is as follows:

主线程:i = 0 
子线程:i = 0 
主线程:i = 1 
子线程:i = 1 
子线程:i = 2 
主线程:i = 2 
子线程:i = 3 
主线程:i = 3 
子线程:i = 4 
主线程:i = 4

[Demo2]: Passing Parameters to the Thread Function

Example

#include "stdafx.h" #include <windows.h> #include <iostream> using namespace std; #define NAME_LINE 40 //define the structure for the 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 threads CloseHandle(hThread1); CloseHandle(hThread2); //execution path of the main thread for (int i = 0; i < 5; ++ i) { cout << "main thread ===" << i << endl; Sleep(100); } system("pause"); return 0; }

Result:

主线程 === 线程1 — 0 
0 
线程2 — 0 
线程1 — 1 
主线程 === 1 
线程2 — 1 
主线程 === 2 
线程1 — 2 
线程2 — 2 
主线程 === 3 
线程2 — 3 
线程1 — 3 
主线程 === 4 
线程2 — 4 
线程1 — 4 
线程2 — 5 
请按任意键继续… 线程2 — 6 
线程2 — 7 
线程2 — 8 
线程2 — 9

CreateMutex、WaitForSingleObject、ReleaseMutex

From [Demo2], we can see that although the created child threads all executed normally, the output result was not what we expected. We expected each output statement to be followed by a newline, but the results were not all like that. This is because no synchronization was performed during thread execution. For example, in the first line of output, after the main thread outputs "main thread ===", its time slice is used up; then it is child thread 1's turn to output. After child thread 1 outputs "thread 1 —", its time slice is also used up; then it is the main thread's turn to output "0", and then child thread 1 outputs "0". Thus the result "main thread === thread 1 — 0 0" appears.

Main thread: cout << "main thread === " << i << endl;
Child thread: cout << pThreadData->strThreadName << " — " << i << endl;

To avoid this situation, we perform some simple synchronization on the threads. Here we use a mutex.

A mutex is similar to a binary semaphore, where a resource is only allowed to be accessed by one thread. Unlike a binary semaphore — where a semaphore can be acquired and released by any thread in the whole system, that is, the same semaphore can be acquired by one thread and released by another — a mutex requires that the thread that acquired the mutex lock must be the one to release it. It is invalid for other threads to release the mutex on its behalf.

When using mutexes for thread synchronization, the following functions are used:

HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        //线程安全相关的属性,常置为NULL
    BOOL                  bInitialOwner,            //创建Mutex时的当前线程是否拥有Mutex的所有权
    LPCTSTR               lpName                    //Mutex的名称
);

Description:lpMutexAttributes is also a security structure, with the same function as lpThreadAttributes in CreateThread, indicating whether the returned handle can be inherited by child processes. If NULL, the returned handle cannot be inherited by child processes. bInitialOwner indicates whether the current thread owns the Mutex when creating it. If TRUE, the current creating thread is designated as the owner of the Mutex object, and other threads need to call ReleaseMutex before accessing it. lpName is the name of the Mutex.

DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                             //要获取的锁的句柄
    DWORD  dwMilliseconds                           //超时间隔
);

Description:WaitForSingleObject waits for a specified object (such as a Mutex object) until the object is in a non-occupied state (such as the Mutex object being released) or the set time interval has elapsed. In addition, there is a similar function, WaitForMultipleObjects, which waits for one or all specified objects until all objects are in a non-occupied state, or until the set time interval has elapsed.

hHandleThe handle of the specified object to wait for. dwMilliseconds: The timeout interval, in milliseconds; if dwMilliseconds is non-zero, it waits until the dwMilliseconds interval has elapsed or the object becomes non-occupied. If dwMilliseconds is INFINITE, it means waiting indefinitely until the waited object is in a non-occupied state.

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

Description:Releases the owned mutex lock object. 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 the 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 to acquire 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, "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 threads CloseHandle(hThread1); CloseHandle(hThread2); //execution path of the main thread for (int i = 0; i < 5; ++ i) { //request to acquire 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:

主线程 === 0 
线程1 — 0 
线程2 — 0 
主线程 === 1 
线程1 — 1 
线程2 — 1 
主线程 === 2 
线程1 — 2 
线程2 — 2 
主线程 === 3 
线程1 — 3 
线程2 — 3 
主线程 === 4 
线程1 — 4 
请按任意键继续… 线程2 — 4 
线程2 — 5 
线程2 — 6 
线程2 — 7 
线程2 — 8 
线程2 — 9

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

Buying train tickets is the thing everyone cares most about when returning home for the Spring Festival. Let's simply simulate a train ticket selling system (to keep the program simple, we 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 a Train Ticketing 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 the 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<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 to acquire a mutex lock WaitForSingleObject(g_hMutex, INFINITE); if (pSaleData->nCount > 0) { cout << pThreadData->strThreadName << "Selling the" << pSaleData->nCount -- << "th ticket,"; if (pSaleData->nCount >= 0) { cout << "Ticket sold successfully! Remaining" << pSaleData->nCount << "tickets." << endl; } else { cout << "Ticket sale failed! The ticket is sold out." << endl; } } Sleep(10); //release the mutex lock ReleaseMutex(g_hMutex); } return 0L; }

Test program:

//售票系统
void Test2()
{
    //创建一个互斥量
    g_hMutex = CreateMutex(NULL, FALSE, NULL);

    //初始化火车票
    TICKET ticket;
    ticket.nCount = 100;
    strcpy(ticket.strTicketName, "北京-->赣州");

    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 = "窗口" + strThreadName;

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

        //创建线程
        hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);

        //请求获得一个互斥量锁
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << threadSale[i].strThreadName << "开始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;
        //释放互斥量锁
        ReleaseMutex(g_hMutex);

        //关闭线程
        CloseHandle(hThread[i]);
    }

    system("pause");
}

Result:

窗口0开始出售 北京–>赣州 的票… 
窗口0出售第100的票,出票成功!剩余99张票. 
窗口1开始出售 北京–>赣州 的票… 
窗口1出售第99的票,出票成功!剩余98张票. 
窗口0出售第98的票,出票成功!剩余97张票. 
窗口2开始出售 北京–>赣州 的票… 
窗口2出售第97的票,出票成功!剩余96张票. 
窗口1出售第96的票,出票成功!剩余95张票. 
窗口0出售第95的票,出票成功!剩余94张票. 
窗口3开始出售 北京–>赣州 的票… 
窗口3出售第94的票,出票成功!剩余93张票. 
窗口2出售第93的票,出票成功!剩余92张票. 
窗口1出售第92的票,出票成功!剩余91张票. 
窗口0出售第91的票,出票成功!剩余90张票. 
窗口4开始出售 北京–>赣州 的票… 
窗口4出售第90的票,出票成功!剩余89张票. 
窗口3出售第89的票,出票成功!剩余88张票. 
窗口2出售第88的票,出票成功!剩余87张票. 
窗口1出售第87的票,出票成功!剩余86张票. 
窗口0出售第86的票,出票成功!剩余85张票. 
窗口5开始出售 北京–>赣州 的票… 
窗口5出售第85的票,出票成功!剩余84张票. 
窗口4出售第84的票,出票成功!剩余83张票. 
窗口3出售第83的票,出票成功!剩余82张票. 
窗口2出售第82的票,出票成功!剩余81张票.

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