悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断当前数据在更新前有没有被修改过。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不相等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
介绍
自旋锁与互斥锁比较类似,自旋锁也是为实现保护共享资源而提出一种锁机制。
两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元占用,线程就会一直循环尝试去申请锁。
自旋锁使用场景
自旋锁的重要的特性
从这几个特性可以归纳出一个共性:被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。
使用自旋锁,必须包含头文件,并链接库-lpthread
#include
初始化函数
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
功能:初始化自旋锁, 当线程使用该函数初始化一个未初始化或者被destroy过的自旋锁。该函数会为自旋锁申请资源并且初始化自旋锁为unlocked状态。
参数 :
pthread_spinlock_t
:要初始化自旋锁
pshared
取值:
返回值
若成功,返回0;否则,返回错误编号
销毁
int pthread_spin_destroy(pthread_spinlock_t *lock);
功能:用来销毁指定的自旋锁并释放所有相关联的资源(所谓的资源指的是由pthread_spin_init
自动申请的资源),如果调用该函数时自旋锁正在被使用或者自旋锁未被初始化则结果是未定义的。
参数 :
pthread_spinlock_t
:要销毁的自旋锁
返回值
若成功,返回0;否则,返回错误编号
加锁
int pthread_spin_lock(pthread_spinlock_t *lock);
功能:用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁,否则该函数在获得自旋锁之前不会返回。
参数 :
pthread_spinlock_t
:要加锁的自旋锁
返回值
若成功,返回0;否则,返回错误编号
解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);
功能:用来解锁指定的自旋锁.。
参数 :
pthread_spinlock_t
:要加锁的自旋锁
返回值
若成功,返回0;否则,返回错误编号
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。如果给这种代码段加锁,会极大地降低我们程序的效率。
那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
为了解决我们的问题我们先来分析多线程中两个角色的关系:读者,写者。
使用自旋锁,必须包含头文件,并链接库-lpthread
#include
初始化/销毁函数
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:初始化/销毁一个读写锁。
参数:
pthread_rwlock_t
: 读写锁的数据结构;pthread_rwlockattr_t
: 读写锁属性的数据结构,一般直接设置为空。返回值
若成功,返回0;否则,返回错误编号
读者加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
当读者要访问数据时要申请先读者锁,拿到读者锁以后才能访问资源。
写者加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
当写者要访问数据时要申请先写者锁,拿到写者锁以后才能访问资源。
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
无论是读者解锁还是写者解锁都要使用此函数。
我们想要使用正确的使用读写锁就还要简单理解一下读写锁的原理,而读写锁就是要维护好上面的读者与写者的关系。
下面是一段伪代码,我们可以用此来理解读写锁的原理。
下面的读者的逻辑:
下面的写者的逻辑:
简单分析:
注意:写独占,读共享,读锁优先级高
pthread
库里面给我们提供了一个函数可以设置读写优先级,使用man
查不到这个函数,但是可以在pthread.h
头文件中发现,如果我们不设置默认是读者优先。int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为
和PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/
下面的代码,我们让设置了票数等于100
,读者负责查询票数,写者负责减少票数,直到票数为0, 读者与写者都退出。
由于读者优先,这个实验的效果可能不明显,这里我们设置读者为30
个,写者为2
个。
#include
#include
#include
#include
#include
#include
using namespace std;
// 线程的属性
struct ThreadAttr
{
pthread_t _tid;
string _name;
};
// 票数
volatile int ticket = 100;
// 读写锁
pthread_rwlock_t rwlock;
函数相关的逻辑:
// 读写锁属性初始化
void rwattr_init(pthread_rwlockattr_t* pattr, int flag)
{
pthread_rwlockattr_init(pattr);
// flag为0表示读者优先,其他表示写着优先
if (flag == 0)
{
pthread_rwlockattr_setkind_np(pattr, PTHREAD_RWLOCK_PREFER_READER_NP);
}
else
{
pthread_rwlockattr_setkind_np(pattr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
}
}
// 读写锁属性的销毁
void rwattr_destroy(pthread_rwlockattr_t* prwlock)
{
pthread_rwlockattr_destroy(prwlock);
}
// 锁的初始化
void rwlock_init(pthread_rwlock_t* prwlock, int flag = 0)
{
pthread_rwlockattr_t rwattr;
rwattr_init(&rwattr, flag);
pthread_rwlock_init(prwlock, &rwattr);
rwattr_destroy(&rwattr);
}
// 创建name
const string create_writer_name(size_t i)
{
stringstream ssm("thread writer : ", ios::in | ios::out | ios::ate);
ssm << i;
return ssm.str();
}
const string create_reader_name(size_t i)
{
stringstream ssm("thread reader : ", ios::in | ios::out | ios::ate);
ssm << i;
return ssm.str();
}
// 读者历程
void* readerRoutine(void* args)
{
string* ps = static_cast<string*>(args);
// 进行查票
while (true)
{
pthread_rwlock_rdlock(&rwlock);
if (ticket != 0)
{
cout << *ps << " ticket number : " << ticket << endl;
}
else
{
cout << *ps << " done!!!!!" << endl;
// 防止死锁
pthread_rwlock_unlock(&rwlock);
break;
}
pthread_rwlock_unlock(&rwlock);
// 休眠0.1ms
usleep(100);
}
}
// 写者历程
void* writerRoutine(void* args)
{
string* ps = static_cast<string*>(args);
// 进行改票
while (true)
{
pthread_rwlock_wrlock(&rwlock);
if (ticket != 0)
{
cout << *ps << " ticket number : " << --ticket << endl;
}
else
{
cout << *ps << " done!!!!!" << endl;
// 防止死锁
pthread_rwlock_unlock(&rwlock);
break;
}
pthread_rwlock_unlock(&rwlock);
// 休眠0.1ms
usleep(100);
}
}
void reader_init(vector<ThreadAttr>& readers)
{
int i = 1;
for (auto& e : readers)
{
e._name = create_reader_name(i++);
pthread_create(&e._tid, nullptr, readerRoutine, &e._name);
}
}
void writer_init(vector<ThreadAttr>& writers)
{
int i = 1;
for (auto& e : writers)
{
e._name = create_writer_name(i++);
pthread_create(&e._tid, nullptr, writerRoutine, &e._name);
}
}
void reader_join(const vector<ThreadAttr>& readers)
{
for (auto& e : readers)
{
pthread_join(e._tid, nullptr);
}
}
void writer_join(const vector<ThreadAttr>& writers)
{
for (auto& e : writers)
{
pthread_join(e._tid, nullptr);
}
}
主函数逻辑:
int main()
{
// 初始化锁,并设置读写者优先属性
rwlock_init(&rwlock, 0);
const int reader_count = 30;
const int writer_count = 2;
vector<ThreadAttr> readers(reader_count);
vector<ThreadAttr> writers(writer_count);
reader_init(readers);
writer_init(writers);
reader_join(readers);
writer_join(writers);
return 0;
}
在读者优先的情况下运行:
在写者优先的情况下运行: