文章目录
- 🚩互斥背景概念
- 🚩线程互斥
-
- 🚩互斥量接口
-
- 初始化互斥量
- 互斥量的加锁与解锁
- 实例
-
- 🚩细节
-
- 饥饿
- 🚩锁的原理
- 🚩RAII风格锁
🚩互斥背景概念
- 临界资源:多线程执行流共享的资源叫临界资源,
- 临界区:每个线程内部,访问临界资源的代码,叫临界区
- 互斥:任何时刻, 只有一个执行流访问临界区,对临界资源起保护作用,
- 原子性:只有两种状态,完成和未完成,没有正在完成
🚩线程互斥
为了防止上篇文章抢到负票情况,我们对临界区代码加锁,确保只有一个执行流访问临界区 互斥量就是实现加锁的一种工具 
🚩互斥量接口
初始化互斥量
两种方式 1,静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
2,函数调用,动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数: mutex:要初始化的互斥量 attr:NULL
互斥量的加锁与解锁
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值: 成功返回0,失败返回错误码
实例
int main()
{
pthread_mutex_t lock;
pthread_mutex_init(&lock,nullptr);
vector<pthread_t> tids;
vector<threadDate*> threadDates;
for(int i=1;i<=NUM;i++)
{
pthread_t tid;
threadDate* td=new threadDate(i,&lock);
threadDates.push_back(td);
pthread_create(&tid,nullptr,getTicket,threadDates[i–1]);
tids.push_back(tid);
}
sleep(5);
for(auto tid:tids)
{
pthread_join(tid,nullptr);
}
for(auto td:threadDates)
{
delete td;
}
pthread_mutex_destroy(&lock)
return 0;
}
main的栈开辟空间存放lock,初始化lock, 怎么让每个线程看到同一把锁? 第一种:类中定义锁,将这把锁传递过去
class threadDate{
public:
threadDate(int number,pthread_mutex_t *mutex)
{
name="thread-"+to_string(number);
lock=mutex;
}
public:
string name;
pthread_mutex_t *lock;
};
第二种:定义全局锁
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
加锁的抢票系统,多个线程抢一把锁,抢到了向下执行,没抢到的线程被挂起,原地等待锁
void *getTicket(void* args)
{
threadDate* td=static_cast<threadDate*>(args);
const char* name=td->name.c_str();
while(1)
{
pthread_mutex_lock(td->lock);
if(ticket>0)
{
usleep(100);
printf("who:%s success,%d\\n",name,ticket);
ticket—;
pthread_mutex_unlock(td->lock);
}
else{
pthread_mutex_unlock(td->lock);
break;
}
}
printf("who:%s is quit\\n",name);
return nullptr;
}
🚩细节
临界区被加锁了,可不可以被切换? 当然可以,但是锁在我这里,其他线程执行也是非临界区的代码
其他线程对待临界区就是原子状态,锁被释放或占用,
饥饿

但有一个问题,纯互斥场景会导致线程对锁竞争能力不同,比如我刚锁释放,其他线程还没被唤醒,我又抢到锁了,导致其他线程饥饿,但是某些场景用到纯互斥就用纯互斥 我们抢票不是纯互斥,只是互斥,因为抢了票了之后还要把票加载数据库等,
while(1)
{
pthread_mutex_lock(td->lock);
if(ticket>0)
{
usleep(100);
printf("who:%s success,%d\\n",name,ticket);
ticket—;
pthread_mutex_unlock(td->lock);
}
else{
pthread_mutex_unlock(td->lock);
break;
}
usleep(10); //模拟抢票之后的动作
}
🚩锁的原理
锁的实现必须是原子的,不可能让多个线程同时获取锁 ticket–会汇编成3条语句,不是原子的, 一条汇编语句就是原子的 
将0加载到al寄存器中,交换al和mutex值(原子性),判断如果寄存器值>0,申请锁成功,否则挂起等待,goto lock重新申请锁。锁被申请成功,其他线程带来自己的寄存器的值(0),无论怎么交换,都是0
交换的本质就是,把共享内存中的数据,与CPU寄存器al交换,把数据交给线程硬件上下文(私有的)带走,
把一个共享的锁,通过一条汇编交换到一个线程的上下文中,当前线程就持有锁了,
==释放锁,把1加载到mutex中,==重新竞争
🚩RAII风格锁
把锁封装,
class Mutex{
public:
Mutex(pthread_mutex_t *lock)
:_lock(lock)
{}
void Lock()
{
pthread_mutex_lock(_lock);
}
void Unlock()
{
pthread_mutex_unlock(_lock);
}
~Mutex()
{}
public:
pthread_mutex_t *_lock;
};
class LockGuard{
public:
LockGuard(pthread_mutex_t *lock)
:_mutex(lock)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex _mutex;
};
创建对象自动加锁了,析构函数释放锁
void *getTicket(void* args)
{
threadDate* td=static_cast<threadDate*>(args);
const char* name=td->name.c_str();
while(1)
{
//pthread_mutex_lock(td->lock);
//pthread_mutex_lock(&lock);
{
LockGuard lk(&lock);
if(ticket>0)
{
usleep(100);
printf("who:%s success,%d\\n",name,ticket);
ticket—;
//pthread_mutex_unlock(td->lock);
//pthread_mutex_unlock(&lock);
}
else{
//pthread_mutex_unlock(td->lock);
//pthread_mutex_unlock(&lock);
break;
}
}
usleep(15);
}
printf("who:%s is quit\\n",name);
return nullptr;
}
额外的花括号,为对象创建独立的作用域, 出花括号对象销毁



