自旋锁
- 当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 等待的机制被称为自旋锁(spinlock)
- 自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!
- 但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cpu 的线程又不能获取到 cpu,造成 cpu 的浪费。
递归锁
- foo函数和bar函数都获取了同一个锁,bar函数先进入临界区加了一次锁,当调用foo函数时,又加了一把锁。如果MutexLock锁是个非递归锁,则这个程序会立即死锁。
MutexLock mutex;
void foo() {
mutex.lock(); //第二次加锁,这时会死锁
// do something
mutex.unlock();
}
void bar() {
mutex.lock(); //第一次加锁
// do something
foo();
mutex.unlock();
}
int main() {
bar();
}
Linux下递归锁
锁的属性设置
- 互斥锁属性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr);来初始化,然后可以调用其他的属性设置方法来设置其属性;
- 可以指定是该进程与其他进程的同步还是同一进程内不同的线程之间的同步。可以设置为PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默认是后者,表示进程内使用锁。可以使用:
int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared)
int pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared)
互斥锁的类型:
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
可以用
pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)
实现
#include <pthread.h>
class recursive_mutex_pth
{
pthread_mutex_t m_mutex;
public:
recursive_mutex_pth()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
~recursive_mutex_pth()
{
pthread_mutex_destroy(&m_mutex);
}
void lock()
{
pthread_mutex_lock(&m_mutex);
}
void unlock()
{
pthread_mutex_unlock(&m_mutex);
}
private:
recursive_mutex_pth(const recursive_mutex_pth&); // not defined
recursive_mutex_pth& operator=(const recursive_mutex_pth&); // not defined
};
Windows下递归锁
windows下的互斥量和临界区(关键段)默认支持递归锁。
临界区
每个线程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。
临界区api
void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection);//初始化一个临界区对象
void DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);//删除临界区对象释放由该对象使用的所有系统资源
void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );//进入临界区,相当于Linux下lock
void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );//删除临界区,相当于Linux下unlock
实现
class RecursiveMutex
{
CRITICAL_SECTION m_cs;
public:
RecursiveMutex(void)
{
InitializeCriticalSection(&m_cs);
}
~RecursiveMutex(void)
{
DeleteCriticalSection(&m_cs);
}
void lock()
{
EnterCriticalSection(&m_cs);
}
void unlock()
{
LeaveCriticalSection(&m_cs);
}
private:
RecursiveMutex(const RecursiveMutex&) = delete;
RecursiveMutex& operator=(const RecursiveMutex&) = delete;
};
互斥锁和共享锁(读写锁)
- 共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,直到已释放所有共享锁。获准共享锁的事务只能读数据,不能修改数据。
- 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的锁,直到在事务的末尾将资源上的锁释放为止。获准排他锁的事务既能读数据,又能修改数据。