| 发表于:2007-04-14 11:54:321楼 得分:0 |
需要,下面的来自《windows核心编程》,可能长了点:) atomic access: the interlocked family of functions a big part of thread synchronization has to do with atomic access—a thread 's ability to access a resource with the guarantee that no other thread will access that same resource at the same time. let 's look at a simple example: // define a global variable. long g_x = 0; dword winapi threadfunc1(pvoid pvparam) { g_x++; return(0); } dword winapi threadfunc2(pvoid pvparam) { g_x++; return(0); } i 've declared a global variable, g_x, and initialized it to 0. now let 's say that i create two threads: one thread EXECutes threadfunc1, and the other thread EXECutes threadfunc2. the code in these two functions is identical: they both add 1 to the global variable g_x. so when both threads stop running, you might expect to see the value 2 in g_x. but do you? the answer is…maybe. the way the code is written, you can 't tell what g_x will ultimately contain. here 's why. let 's say that the compiler generates the following code for the line that increments g_x by 1: mov eax, [g_x] ; move the value in g_x into a register. inc eax ; increment the value in the register. mov [g_x], eax ; store the new value back in g_x. both threads are unlikely to EXECute this code at exactly the same time. so if one thread EXECutes this code followed by another thread, here is what effectively EXECutes: mov eax, [g_x] ; thread 1: move 0 into a register. inc eax ; thread 1: increment the register to 1. mov [g_x], eax ; thread 1: store 1 back in g_x. mov eax, [g_x] ; thread 2: move 1 into a register. inc eax ; thread 2: increment the register to 2. mov [g_x], eax ; thread 2: store 2 back in g_x. after both threads are done incrementing g_x, the value in g_x is 2. this is great and is exactly what we expect: take zero (0), increment it by 1 twice, and the answer is 2. beautiful. but wait—windows is a preemptive, multithreaded environment. so a thread can be switched away from at any time and another thread might continue EXECuting at any time. so the code above might not EXECute exactly as i 've written it. instead, it might EXECute as follows: mov eax, [g_x] ; thread 1: move 0 into a register. inc eax ; thread 1: increment the register to 1. mov eax, [g_x] ; thread 2: move 0 into a register. inc eax ; thread 2: increment the register to 1. mov [g_x], eax ; thread 2: store 1 back in g_x. mov [g_x], eax ; thread 1: store 1 back in g_x. if the code EXECutes this way, the final value in g_x is 1—not 2 as you expect! this is pretty scary, especially since you have so little control over the scheduler. in fact, if you have 100 threads EXECuting similar thread functions, after all of them exit, the value in g_x might still be 1! obviously, software developers can 't work in an environment like this. we expect that incrementing 0 twice results in 2 all the time. also, let 's not forget that the results might be different depending on how the compiler generates codes, what cpu is EXECuting the code, and how many cpus are installed in the host computer. this is how the environment works, and there is nothing we can do about that. but windows does offer some functions that, when used correctly, guarantee the outcome of our application 's code. to solve the problem above, we need something simple. we need a way to guarantee that the incrementing of the value is done atomically—that is, without interruption. the interlocked family of functions provides the solution we need. the interlocked functions are awesome and underused by most software developers, even though they are incredibly helpful and easy to understand. all of the functions manipulate a value atomically. take a look at interlockedexchangeadd: long interlockedexchangeadd( plong pladdend, long lincrement); what could be simpler? you call this function, passing the address of a long variable and indicating by how much to increment this value. but this function guarantees that the adding of the value is accomplished atomically. so we can rewrite the code presented earlier as follows: // define a global variable. long g_x = 0; dword winapi threadfunc1(pvoid pvparam) { interlockedexchangeadd(&g_x, 1); return(0); } dword winapi threadfunc2(pvoid pvparam) { interlockedexchangeadd(&g_x, 1); return(0); } by making this small change, g_x is incremented atomically and therefore you are guaranteed that the final value in g_x will be 2. don 't you feel better already? note that all the threads should attempt to modify the shared long variable by calling these functions; no thread should ever attempt to modify the shared variable by using simple c statements: // the long variable shared by many threads long g_x; // incorrect way to increment the long g_x++; // correct way to increment the long interlockedexchangeadd(&g_x, 1); how do the interlocked functions work? the answer depends on the cpu platform that you 're running on. for the x86 family of cpus, interlocked functions assert a hardware signal on the bus that prevents another cpu from accessing the same memory address. on the alpha platform, the interlocked functions do something like this: | | |
|