user2正在进行抢票: 4
user3正在进行抢票: 3
user1正在进行抢票: 2
user4正在进行抢票: 1
user2正在进行抢票: 0
user3正在进行抢票: -1
user1正在进行抢票: -2
int tickets=10000;
void* getTicket(void* args)
{
string username=static_cast<const char*>(args);
while(true)
{
if(tickets>0)
{
usleep(1000);//1s=10^3ms=10^6us=10^ns
cout<<username<<"正在进行抢票: "<<tickets--<<endl;
}
else
{
break;
}
}
}
int main()
{
unique_ptr<Thread> thread1(new Thread(getTicket,(void*)"user1",1));
unique_ptr<Thread> thread2(new Thread(getTicket,(void*)"user2",2));
unique_ptr<Thread> thread3(new Thread(getTicket,(void*)"user3",3));
unique_ptr<Thread> thread4(new Thread(getTicket,(void*)"user4",4));
thread1->join();
thread2->join();
thread3->join();
return 0;
}
取出ticket--部分的汇编代码
objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
-- 操作并不是原子操作,而是对应三条汇编指令:
load :将共享变量ticket从内存加载到寄存器中
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址
要解决以上问题,需要做到三点:
代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临 界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。 要做到这三点,本质上就是需要一把锁。
Linux上提供的这把锁叫互斥量。
锁的初始化和销毁:
将锁定义为局部变量:
pthread_mutex_t lock;
pthread_mutex_init(&lock,nullptr);
pthread_mutex_destroy(&lock);
将锁定义为全局变量:
int tickets=10000;
pthread_mutex_t lock=PTHREAD_MUTEX_INITALIZER;
tickets是一个全局变量被多个线程同时访问时,将该变量称为共享数据,共享数据进过锁的保护称为临界资源,可以保证安全进行访问。
#pragma once
#include<iostream>
#include<pthread.h>
class Mutex
{
public:
Mutex(pthread_mutex_t* lock_p=nullptr)
:lock_p_(lock_p)
{}
void lock()
{
if(lock_p_) pthread_mutex_lock(lock_p_);
}
void unlock()
{
if(lock_p_) pthread_mutex_unlock(lock_p_);
}
~Mutex()
{}
private:
pthread_mutex_t* lock_p_;
};
class LockGuard
{
public:
//构造函数
LockGuard(pthread_mutex_t* mutex)
:mutex_(mutex)
{
mutex_.lock();
}
~LockGuard()
{
mutex_.unlock();
}
private:
Mutex mutex_;
};
int tickets=10000;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
class ThreadData
{
public:
ThreadData(const string& threadname,pthread_mutex_t* mutex_p)
:threadname_(threadname),mutex_p_(mutex_p)
{}
~ThreadData() {}
string threadname_;
pthread_mutex_t* mutex_p_;
};
void* getTicket(void* args)
{
//string username=static_cast<const char*>(args);
ThreadData* td=static_cast<ThreadData*>(args);
while(true)
{
// pthread_mutex_lock(td->mutex_p_);
//pthread_mutex_lock(&lock);
{
LockGuard lockguard(&lock);
if(tickets>0)
{
usleep(1000);//1s=10^3ms=10^6us=10^ns
cout<<td->threadname_<<"正在进行抢票: "<<tickets<<endl;
tickets--;
pthread_mutex_unlock(&lock);
}
else
{
pthread_mutex_unlock(&lock);
break;
}
}
//cout<<"我是一个新线程,我正在做: "<<work_type<<endl;
// sleep(1);
//抢完票之后还需要形成订单给用户
usleep(1000);
}
}
将临界区封装为代码块:
{
LockGuard lockguard(&lock);
if(tickets>0)
{
usleep(1000);//1s=10^3ms=10^6us=10^ns
cout<<td->threadname_<<"正在进行抢票: "<<tickets<<endl;
tickets--;
pthread_mutex_unlock(&lock);
}
else
{
pthread_mutex_unlock(&lock);
break;
}
}
大大提高了加锁效率。
如何看待锁?
a.锁本身就是一个共享资源!!!
b.pthread_mutex_lock:加锁过程必须是安全的!!!加锁过程其实是原子的。
c.如果申请成功就继续向后执行,如果申请暂时没有成功,执行流会阻塞。
d.谁持有锁谁进入临界区
加锁的过程是原子的。
互斥量实现原理探究
经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单 元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一 个处理器上的交换指令执行时
常见不可重入的情况
调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
可重入函数体内使用了静态的数据结构
死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资 源而处于的一种永久等待状态。
死锁的四个必要条件:
1.互斥
2.请求与 保持
3.不剥夺
4.环路等待条件
避免死锁
破坏死锁的四个必要条件
加锁顺序一致
避免锁未释放的场景
资源一次性分配
Linux线程同步
条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情 况就需要用到条件变量。