线程同步之原子操作与旋转锁
锁操作:为了确保操作区域的安全。一般有旋转锁、读写锁、原子操作。
原子操作:原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。
例子:++i是否属于原子操作
答:不是原子操作
解析:
i++分为三个阶段:
1.内存到寄存器
2.寄存器自增
3.写回内存
这三个阶段中间都可以被中断分离开,所以不属于原子操作。
当我们利用线程需要对一个全局变量进行累加、减操作时,怕被中断,可用原子操作。
int g_nCount = 0;
InterlockedExchangeAdd((long*)&g_nCount, 3);
对全局变量&g_nCount每次进行+3操作。
==InterlockedExchangeSubtract((long*)&g_nCount, 2)
InterlockedExchangeAdd((long*)&g_nCount, -2);
此两函数等价。但是InterlockedExchangeSubtract()最终也会调用InterlockedExchangeAdd()此函数。因此可以直接+(-2)。
InterlockedIncrement((long*)&g_nCount);
//对变量每次进行+1操作
InterlockedDecrement((long*)&g_nCount);
//对变量每次进行-1操作
这两个函数都是固定+(-)1,不可以自拟。
旋转锁(Spin Lock):旋转锁是一种非阻塞锁,由某个线程独占。采用旋转锁时,等待线程并不静态地阻塞在同步点,而是必须“旋转”,
不断尝试直到最终获得该锁。
适用:
旋转锁多用于多处理器系统中。这是因为,如果在单核处理器中采用旋转锁,当一个线程正在“旋转”时,将没有执行资源可供另一释放锁的线程使用。
旋转锁适合于任何锁持有时间少于将一个线程阻塞和唤醒所需时间的场合。线程控制的变更,包括线程上下文的切换和线程数据结构的更新。
危害:
在线程调用其他子系统时,线程不应持有旋转锁。对旋转锁的不当使用可能会导致线程饿死,因此需谨慎使用这种锁机制。旋转锁导致的饿死问题可使用排队技术来解决,
即每个等待线程按照先进先出的顺序或者队列结构在一个独立的局部标识上进行旋转
比如:进屋子,如果门开着的,就可以直接进入房子同时将反锁,不允许其他人进行。当自己离开房子时,再将门打开,等待下一个人。
函数解析:
InterlockedExchange((long *)&g_bIsUsing, true)
原型:
LONG InterlockedExchange(
_Inout_ LONG volatile *Target,
_In_ LONG Value
);
InterlockedExchange(a,b)能以原子操作的方式交换俩个参数a, b,并返回a以前的值;因为InterlockedExchange 是原子函数,不会要求中止中断,
所以交换指针的方式是安全的。
问题:传递线程参数时,为什么保存在数组中,而不是直接传递i?
解析:
在循环中,我们创建线程并传递的参数是i=1后,主程序有可能在执到下一次循环时,第一次的ThreadProc函数仍未执行,而此时的i已经=2了,如果ThreadProc再来调用
int nThreadNo = *(int*)lParam;语句时,显然不是我们想要的结果。
解决方法:
可以使用静态数组来保存所要传递的参数。此时,所有参数均保存在threadId数组中。
源代码: #include "stdafx.h" #include <Windows.h> #include <process.h> #define THREADNUM 10 int g_nCount = 0; bool g_bIsUsing = false; unsigned _stdcall ThreadProc(void *lParam) { int nThreadNo = *(int*)lParam; //等待访问共享资源 while (InterlockedExchange((long *)&g_bIsUsing, true) == true) Sleep(0); //访问共享资源 printf("线程%d开始执行\n", nThreadNo); printf("线程%d结束执行\n", nThreadNo); //结束访问 InterlockedExchange((long *)&g_bIsUsing, false); //原子操作 //InterlockedExchangeAdd((long*)&g_nCount, 3); //InterlockedExchangeAdd((long*)&g_nCount, -2); //InterlockedDecrement((long*)&g_nCount); //InterlockedIncrement((long*)&g_nCount); return 0; } int _tmain(int argc, _TCHAR* argv[]) { HANDLE pThreads[THREADNUM]; //保存当前执行线程的ID int threadId[THREADNUM]; //启动线程 for (int i = 0; i < THREADNUM; ++i) { threadId[i] = i; pThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, threadId+i, 0, 0); if (pThreads[i] == 0) { continue; i--; } //休眠,可保证上一线程执行完成。即按序号执行 //Sleep(100); } WaitForMultipleObjects(THREADNUM, pThreads, TRUE, INFINITE); //通过原子操作,最后累加结果 //printf("g_nCount:%d", g_nCount); //释放资源 for (int i = 0; i < THREADNUM; ++i) { CloseHandle(pThreads[i]); } getchar(); return 0; }