互斥锁
悲观锁(Pessimistic Locking)
悲观并发控制,又名悲观锁
在编程语言中,悲观锁可能存在以下缺陷:
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 一个线程持有锁会导致其它所有需要此锁的线程挂起。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
数据库中悲观锁主要由以下问题:悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受,特别是对长事务而言。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时
互斥锁/ 排他锁
互斥锁即对互斥量进行分加锁,和自旋锁类似,唯一不同的是竞争不到锁的线程会回去睡会觉,等到锁可用再来竞争,第一个切入的线程加锁后,其他竞争失败者继续回去睡觉直到再次接到通知、竞争。
互斥锁算是目前并发系统中最常用的一种锁,POSIX、C++11、
struct mutex {
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
struct task_struct *owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
This structure is defined in the include/linux/mutex.h header file and contains similar to the semaphore structure set of fields. The first field of the mutex structure is - count. Value of this field represents state of a mutex. In a case when the value of the count field is 1, a mutex is in unlocked state. When the value of the count field is zero, a mutex is in the locked state. Additionally value of the count field may be negative. In this case a mutex is in the locked state and has possible waiters.
互斥
互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源。
-
创建
Create -
加锁
Lock -
解锁
Unlock -
销毁
Destroy
不同操作系统中提供的
创建 CreateMutex pthread_mutex_init mutex_init
加锁 WaitForSingleObject pthread_mutex_lock mutex_lock
解锁 ReleaseMutex pthread_mutex_unlock mutex_unlock
销毁 CloseHandle pthread_mutex_destroy mutex_destroy
互斥量本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行枷锁以后,其他视图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程就会看到互斥量依然是锁着,只能再次阻塞等待它重新变成可用,这样,一次只有一个线程可以向前执行。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);//互斥初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥
int pthread_mutex_lock(pthread_mutex_t *mutex);//锁定互斥
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁互斥
int pthread_mutex_trylock(pthread_mutex_t *mutex);//销毁互斥
pthread_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
// ...
pthread_mutex_unlock(&mutex);
pthread_mutex_detroy(&mutex);
可递归锁/ 可重入锁
也叫做锁递归,就是获取一个已经获取的锁。不支持线程获取它已经获取且尚未解锁的方式叫做不可递归或不支持重入。带重入特性的锁在重入时会判断是否同一个线程,如果是,则使持锁计数器
在所有的线程同步方法中,恐怕互斥锁
MutexLock mutex;
void foo()
{
mutex.lock();
// do something
mutex.unlock();
}
void bar()
{
mutex.lock();
// do something
foo();
mutex.unlock();
}
// 不加锁版本
void foo_nolock()
{
// do something
}
// 加锁版本
void fun()
{
mutex.lock();
foo_nolock();
mutex.unlock();
}
为了接口的将来的扩展性,可以将
class T
{
public:
foo(); //加锁
bar(); //加锁
private:
foo_nolock();
bar_nolock();
}
条件变量
条件变量是线程可用的另一种同步机制。互斥量用于上锁,条件变量则用于等待,并且条件变量总是需要与互斥量一起使用,运行线程以无竞争的方式等待特定的条件发生。
条件变量本身是由互斥量保护的,线程在改变条件变量之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种变化,因为互斥量必须在锁定之后才能计算条件。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);//初始化条件变量
int pthread_cond_destroy(pthread_cond_t *cond);//销毁条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//无条件等待条件变量变为真
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *tsptr);//在给定时间内,等待条件变量变为真
eg.pthread_mutex_t mutex;
pthread_cond_t cond;
// ...
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
// ...
pthread_mutex_unlock(&mutex);
// ...
注意,
读写锁
读写锁与互斥量类似,不过读写锁拥有更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr);//初始化读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式锁定读写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式锁定读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁读写锁
eg.pthread_rwlock_t q_lock;
pthread_rwlock_init(&q_lock, NULL);
pthread_rwlock_rdlock(&q_lock);
...
pthread_rwlock_unlock(&q_lock);
pthread_rwlock_detroy(&q_lock);
支持两种模式的锁,当采用写模式上锁时与互斥锁相同,是独占模式。但读模式上锁可以被多个读线程读取。即写时使用互斥锁,读时采用共享锁,故又叫共享
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <list>
bool quitting = false;
int data = 0;
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP;
class Reader {
int id_;
pthread_t thread_;
public:
explicit Reader(int id) :
id_(id)
{
pthread_create(&thread_, nullptr, &Reader::_run, this);
}
~Reader() {
pthread_join(thread_, nullptr);
}
void run() {
while (!quitting) {
pthread_rwlock_rdlock(&rwlock);
printf("[%d]: %d\n", id_, data);
pthread_rwlock_unlock(&rwlock);
pthread_yield(); // junk
}
}
private:
static void* _run(void* p) {
Reader* reader = static_cast<Reader*>(p);
reader->run();
return nullptr;
}
};
void writer() {
pthread_yield();
for (;;) {
sleep(1);
printf("rwlock: Acquiring for WRITE\n");
pthread_rwlock_wrlock(&rwlock);
printf("rwlock: hardcore WRITE-action @.@ BEGIN\n");
data = data + 1;
quitting = data == 42;
sleep(2);
printf("rwlock: hardcore WRITE-action @.@ DONE\n");
pthread_rwlock_unlock(&rwlock);
}
}
int main(int argc, const char *argv[])
{
srand(time(nullptr));
std::list<Reader*> readers;
for (int i = 1, e = (argc == 2 ? atoi(argv[1]) : 4); i <= e; ++i)
readers.push_back(new Reader(i));
writer();
for (auto reader: readers)
delete reader;
return 0;
}