几个常见的锁
- 多线程中如链表等数据结构的同步问题是进行驱动开发时必须所考虑到的问题,这个时候就必须使用到锁。
读写锁
- 特点:
- 读写所有三种状态:读加锁状态、写加锁状态和不加锁状态。
- 其中,只有一个线程可以占有写状态的锁,但是可以有多个线程占用读状态的锁,这也是它可以实现高并发的原因。
- 当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放 ;为了避免想要尝试写操作的线程一致得不到写状态锁,当处于读模式的读写锁收到一个试图对其进行写模式的加锁操作时,便会阻塞后面所有向获得读状态锁的线程, 这样当读模式的锁解锁后,要获得写状态锁的线程能够访问此锁保护的资源。
- 读写锁非常适合资源的读操作远远多于写操作的情况。
互斥锁
- 特点:
- 在访问共享资源之前进行加锁操作,访问资源完毕后进行解锁操作。枷锁后,任何其它试图再次加锁的线程都会被阻塞,直到当前占用锁的线程解锁。
- 如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。
自选锁
-
自旋锁(Spin lock)特点:
- 和互斥锁类似,不过其不会引起调用者睡眠,所以效率远高于互斥锁,如果自选锁已经被别的执行单元保持,那么调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源,适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。
-
使用如下代码可以初始化一个自旋锁:
KSPIN_LOCK spinLock;
keInitlizeSpinLock(&spinLock);
//这个函数无返回值
-
自选锁的使用用到了两个函数,
KeAcquireSpinLock
获得一个自旋锁也就是加锁操作和KeRelseaseSpinLock
释放自旋锁也就是解锁。使用
KeAcquireSpinLock
时会提高当前中断级别,旧的中断级别需要定义一个KIRQL
类型的变量进行保存,以便于重新设置回中断级别。 -
具体代码如下:
//要注意,多线程使用自旋锁同步时
//定义的自选锁必须是一个全局的
//不然是没有任何意义的
KSPIN_LOCK g_spinLock;
VOID MySafeThreadProc(PVOID context)
{
//一个线程回调
KeInitializeSpinLock(&g_spinLock);
KIRQL oldIrql;
KeAcquireSpinLock(&g_spinLock,&oldIrql);
//do something ...
KeReleaseSpinLock(&g_spinLock,oldIrql);
}
- 这样诸如前面讲解的链表操作,在链表初始化后,就可以采用一些列加锁的操作来代替普通的操作:
//普通的链表插入函数如下
InsertHeadList(&myListHead,(PLIST_ENTRY)&myFileInfo);
//加锁后的操作方式如下:
ExInterlockedInsertHeadList(&myListHead,(PLIST_ENTERY)&myFileInfo,&g_spinLock);
//类似的还有一个移除节点的操作也可以改变
myFileInfo=ExInterlockedRemoveHeadList(&myListHead,&g_spinLock);
//都是在原先函数的基础上加上了一个自旋锁参数,用于保证线程安全
使用队列自旋锁提高性能
- 队列自旋锁(Queued spin lock):是WindowsXP之后被引入的,他在多CPU平台上具有更高的性能表现,并且遵守“first-come first-served”原则,即-“谁先等待,谁先获取自旋锁”的原则。
- 队列自旋锁初始化和普通自旋锁一样使用
KeInitializeSpinLock
函数,不同之处在与获取和释放自旋锁时需要使用新的函数:
VOID KeAcquireInStackQueuedSpinLock(
IN PKSPIN_LOCK SpinLock,
In PKLOCK_QUEUE_HANDLE LockHandle
);
VOID KeReleaseInStackQueuedSpinLock(
IN PKLOCK_QUEUE_HANDLE LockHandle
);
//下面介绍用法:
//1. 初始化一个队列自旋锁
KSPIN_LOCK queueSpinLock={0};
KeInitializeSpinLock(&queueSpinLock);
//2. 队列自旋锁获取
KLOCK_QUEUE_HANDLE lockQueueHandle;
KeAcquireInstackQueuedSpinLock(&queueSpinLock,&lockQueueHandle);
//do something...
//3. 释放
KeReleaseInstackQueuedSpinLock(&lockQueueHandle);
- 可以看出,队列自旋锁增加了一个
KLOCK_QUEUE_HANDLE
数据结构,这个结构唯一地表示一个队列自旋锁。 - 虽然两种自旋锁都使用同一个初始化函数初始化,但是,两者绝对不能混用。