Do not use shared variable to check thread status



//excerpt from Windows via c/c++, chapter8, Advanced Thread Synchronization

A Technique to Avoid
Without synchronization objects and the operating system's ability to watch for special events, a thread would be forced to synchronize itself with special events by using the technique that I am about to demonstrate. However, because the operating system has built-in support for thread synchronization, you should never use this technique.

In this technique, one thread synchronizes itself with the completion of a task in another thread by continuously polling the state of a variable that is shared by or accessible to multiple threads. The following code fragment illustrates this:

volatile BOOL g_fFinishedCalculation = FALSE;

int WINAPI _tWinMain(...) {
   CreateThread(..., RecalcFunc, ...);
   ...
   // Wait for the recalculation to complete.
   while (!g_fFinishedCalculation)
        ;
   ...
}

DWORD WINAPI RecalcFunc(PVOID pvParam) {
   // Perform the recalculation.
   ...    g_fFinishedCalculation = TRUE;
   return(0);
}

As you can see, the primary thread (executing _tWinMain) doesn't put itself to sleep when it needs to synchronize itself with the completion of the RecalcFunc function. Because the primary thread does not sleep, it is continuously scheduled CPU time by the operating system. This takes precious time cycles away from other threads.

Another problem with the polling method used in the previous code fragment is that the BOOL variable g_fFinishedCalculation might never be set to TRUE. This can happen if the primary thread has a higher priority than the thread executing the RecalcFunc function. In this case, the system never assigns any time slices to the RecalcFunc thread, which never executes the statement that sets g_fFinishedCalculation to TRUE. If the thread executing the _tWinMain function is put to sleep instead of polling, it is not scheduled time and the system can schedule time to lower-priority threads, such as the RecalcFunc thread, allowing them to execute.

I'll admit that sometimes polling comes in handy. After all, this is what a spinlock does. But there are proper ways to do this and improper ways to do this. As a general rule, you should not use spinlocks and you should not poll. Instead, you should call the functions that place your thread into a wait state until what your thread wants is available. I'll explain a proper way in the next section.

First, let me point out one more thing: At the top of the previous code fragment, you'll notice the use of volatile. For this code fragment to even come close to working, the volatile type qualifier must be there. This tells the compiler that the variable can be modified by something outside of the application itself, such as the operating system, hardware, or a concurrently executing thread. Specifically, the volatile qualifier tells the compiler to exclude the variable from any optimizations and always reload the value from the variable's memory location. Let's say that the compiler has generated the following pseudocode for the while statement shown in the previous code fragment:

MOV   Reg0, [g_fFinishedCalculation]  ; Copy the value into a register
Label: TEST Reg0, 0                   ; Is the value 0?
JMP   Reg0 == 0, Label                ; The register is 0, try again
...                                   ; The register is not 0 (end of loop)

Without making the Boolean variable volatile, it's possible that the compiler might optimize your C++ code as shown here. For this optimization, the compiler loads the value of the BOOL variable into a CPU register just once. Then it repeatedly performs tests against the CPU register. This certainly yields better performance than constantly rereading the value in a memory address and retesting it; therefore, an optimizing compiler might write code like that just shown. However, if the compiler does this, the thread enters an infinite loop and never wakes up. By the way, making a structure volatile ensures that all of its members are volatile and are always read from memory when referenced.

You might wonder whether my spinlock variable, g_fResourceInUse (used in the spinlock code shown on page 211), should be declared as volatile. The answer is no because we are passing the address of this variable to the various interlocked functions and not the variable's value itself. When you pass a variable's address to a function, the function must read the value from memory. The optimizer cannot affect this.

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值