Learning Win32 multithreaded programming (Chapter 3)

Run and wait

In this chapter, we revisited the bad results of busy loops and learned how to use Windows NT's performance monitor to catch problems. We also learned about the so-called "excited state objects" and learned how to wait for one or more such objects in a worker thread or a GUI thread. Finally, we saw how to rebuild a main message loop so that MsgWaitForMultipleObjects() can be used appropriately.

1. Seemingly leisure but Busy Waiting

Example in the article: calculate PI

The first case: directly call the function to calculate the PI.

The second case: start a thread to run the function to calculate the PI, and the main thread keeps waiting for the worker thread to finish the while loop.

Results: the time consumed by the second method was twice as long as that of the first method.

Reason: it is the impact of preemptive multitasking. The operating system has no way to judge which thread is useful and which thread is useless, so each thread gets the same CPU time.

2. Performance Monitor

3. Wait for the end of a thread

DWORD WaitForSingleObject(
 HANDLE hHandle,
 DWORD dwMilliseconds
);
parameter
hHandle Waiting for objects handle (Represents a core object). In Ben
 In this example, this is a thread handle . 
dwMilliseconds Maximum waiting time. At the end of time, even if handle Not yet
 This function still returns if it is in the firing state. This value can be 0
(For immediate return), or INFINITE Representative none
 Poor waiting.

Return value:

If the function fails, wait is returned_ FAILED. At this time, you can call GetLastError() to get more information. The success of this function has three factors:

1. The waiting target (core object) becomes excited. In this case, the return value will be WAIT_OBJECT_0

2. The waiting time ends before the core object becomes excited. In this case, the return value will be WAIT_TIMEOUT.

3. If a thread with mutex (mutex) does not release mutex before it ends, it returns WAIT_ ABANDONED. Mutexes will be discussed more in Chapter 4.

After obtaining the handle of a thread object, WaitForSingleObject() requires the operating system to let the thread #1 sleep until any of the following occurs:

i thread #2 end

i dwMilliseconds time ends.

The value is calculated after the function call. Because the operating system keeps track of threads #2, WaitForSingleObject() works even if the thread #2 crashes or is forced to end.

How do I know if a core object is fired?

Setting time out to 0 allows you to check the status of the handle and return immediately without a moment's pause. If the handle is ready, the function succeeds and returns WAIT_OBJECT_0 Otherwise, this function immediately returns and returns WAIT_TIMEOUT .

4. Signaled Objects

1) What is an excited object?

1 > the core object has two states: fired and not fired

2 > when the core object is fired, it will cause WaitForSingleObject() to wake up. The same is true for the other Wait() functions you will see later

For example, when a thread is executing, the thread object is not fired. When the thread ends, the thread object is fired.

2) What are the different meanings of "excitation" for different core objects? (form from win32)

5. Wait for multiple objects

DWORD WaitForMultipleObjects(
 DWORD nCount,
 CONST HANDLE *lpHandles,
 BOOL bWaitAll,
 DWORD dwMilliseconds
);
parameter
--------------------------------------------------------------
nCount         |     express lpH andles Refers to handles Array elements
               |    Number. The maximum capacity is MAXIMUM_WAIT_OBJECTS . 
--------------------------------------------------------------
lpHandles      |    Point to an object handles An array of. these
--------------------------------------------------------------
handles        |     You do not need to be of the same type.
--------------------------------------------------------------
bWaitAll       |     If this is TRUE ,Indicates all handles Must be excited
               |     This function can return only after sending. Otherwise, this function will
--------------------------------------------------------------
handle         |     Returns when fired.
--------------------------------------------------------------
dwMilliseconds |     When this length of time ends, even if there is no handles Excite
               |     This function will also return. This value can be 0 for testing purposes. also
               |     Can be specified as INFINITE ,Indicates infinite wait.
--------------------------------------------------------------

The return value of WaitForMultipleObjects() is somewhat complex.

i if it is returned due to the end of time, the return value is WAIT_TIMEOUT, similar to WaitForSingleObject().

If bWaitAll is TRUE, the return value will be WAIT_OBJECT_0

If bWaitAll is FALSE, then wait is subtracted from the return value_ OBJECT_ 0, which handle in the array is fired.

If there are any mu texes in the object you are waiting for, the return value may be from WAIT_ABANDONED_0 to WAIT_ABANDONED_0 + nCount - 1.

i) if the function fails, it returns WAIT_FAILED . At this time, you can use GetLastError() to find out the cause of the failure.  

Note that there is an upper limit on the number of elements in the handles array, which must not exceed MAXIMUM_WAIT_OBJECTS. On Windows NT 3.0 In X and 4.0, the value is 64.  

DWORD WINAPI P3_2_ThreadFunc(LPVOID n);

#define  THREAD_POOL_SIZE 3
#define MAX_THREAD_INDEX THREAD_POOL_SIZE-1 
#define NUM_TASKS 6

void P3_2_TaskQueSFunc()
{
	HANDLE	hThrds[THREAD_POOL_SIZE];
	DWORD	threadID;
	DWORD	exitCode;
	int			iSlot = 0;
	int			rc = 0;

	for (int i = 0; i < NUM_TASKS; i++)
	{
		if (i > MAX_THREAD_INDEX)
		{
			rc = WaitForMultipleObjects(THREAD_POOL_SIZE,
				hThrds,
				FALSE,
				INFINITE);
			iSlot = rc - WAIT_OBJECT_0;
			MTVERIFY(iSlot >= 0
				&& iSlot < THREAD_POOL_SIZE);
			printf("Slot %d terminated\n", iSlot);
			MTVERIFY(CloseHandle(hThrds[iSlot]));
		}
		MTVERIFY(hThrds[iSlot++] = CreateThread(NULL,
			0,
			P3_2_ThreadFunc,
			(LPVOID)iSlot,
			0,
			&threadID));
	}

	MTVERIFY(rc = WaitForMultipleObjects(THREAD_POOL_SIZE,
		hThrds,
		TRUE,
		INFINITE));
	MTVERIFY(rc >= WAIT_OBJECT_0
		&& rc < WAIT_OBJECT_0 + THREAD_POOL_SIZE);

	for (iSlot = 0; iSlot < THREAD_POOL_SIZE; iSlot++)
	{
		MTVERIFY(CloseHandle(hThrds[iSlot]));
	}
	printf("All Thread Terminated\n");
}

DWORD WINAPI  P3_2_ThreadFunc(LPVOID n)
{
	srand(GetTickCount());	//Starting from the seed specified in srand (seed), returns a random integer between [seed, RAND_MAX (0x7fff)).
	Sleep((rand() % 10) * 800 + 500);
	printf("Slot %d idle\n", n);
	return (DWORD)n;
}

6. Wait in a GUI program

1) How do I wait for a handle in the main thread?

The standard message loop in a Windows program looks like this:

while (GetMessage(&msg, NULL, 0, 0,))
{
 TranslateMessage(&msg);
 DispatchMessage(&msg);
} 

GetMessage() is a bit like a special version of WaitForSingleObject(), which waits for messages instead of core objects. Once you call GetMessage (), it will not return unless a message actually enters your message queue. During this period, Windows is free to give CPU time to other programs.

Problem: the problem is that if you are using waitforsingleo object() or WaitForMultipleObjects() to wait for an object to be fired, you simply can't go back to the main message loop. At this time, the window will stop redrawing, your program menu will no longer work, and things users don't like will slowly begin to happen.

The following approach does not really solve the problem:

When the main thread is processing the main message loop, the second thread is not used to wait for handles. Because if you do this and just move the problem from one place to another, you still have to decide whether to use the "polling" method or a Win32 Wait () function to detect the end of the new thread.

2) MsgWaitForMultipleObjects() function

This function is very similar to WaitForMultipleObjects(), but it returns when the "object is fired" or "message arrives on the queue". MsgWaitForMultipleObjects() accepts an extra parameter that allows you to specify which messages are observation objects.

DWORD MsgWaitForMultipleObjects(
 DWORD nCount,
 LPHANDLE pHandles,
 BOOL fWaitAll,
 DWORD dwMilliseconds,
 DWORD dwWakeMask
);
parameter
dwWakeMask The user input message to be observed can be:

                    QS_ALLINPUT
                    QS_HOTKEY
                    QS_INPUT
                    QS_KEY
                    QS_MOUSE
                    QS_MOUSEBUTTON
                    QS_MOUSEMOVE 
                    QS_PAINT
                    QS_POSTMESSAGE
                    QS_SENDMESSAGE
                    QS_TIMER 

Return value

MsgWaitForMultipleObjects() has some additional return value meaning compared to WaitForMultipleObjects(). To indicate "message arrival queue", the return value will be WAIT_OBJECT_0 + nCount.

code:

 

There are several situations that this loop must deal with but may be easily ignored in its first design:

1. After you receive WM_ After quit, Windows will still send messages to you. If you want to receive WM_ After quit, wait for all threads to end. You must continue to process your messages, otherwise the window will become slow and have no redrawing ability.

2. MsgWaitForMultipleObjects() does not allow gaps in the handles array. Therefore, when a handle is fired, you should tidy up and compact the handles array before calling MsgWaitForMultipleObjects() next time. Don't just set the handle in the array to NULL. If you look closely at the above program code, you will find that I move the tail handle to the empty position, and then reduce the size of the array by 1.

3. If another thread changes the object array and that is what you are waiting for, you need a method to force MsgWaitForMultipleObjects() to return and restart to include the new handle. The solution in listing 3-5 is to use WM_ THREADCOUNT message.

 

 

 

Keywords: C++

Added by muinej on Thu, 06 Jan 2022 14:22:50 +0200