目录
编辑
一,锁的概念
二,锁的操作
1,锁类型 pthread_mutex_t
2,初始化锁
3,上锁
4,解锁
5,销毁锁
三,线程安全问题演示
四,锁的原理
五,死锁
1,死锁的概念
2,死锁的必要条件
3,一个线程自己也能造成死锁
一,锁的概念
在多线程当中。当多个线程并发的(在一段时间内一起访问一个公共资源)访问一个公共资源时就会导致数据不一致问题。所以为了避免这个问题(线程安全问题),所以要引入锁的概念,让这个公共资源在一段时间内变成一个临界资源(一段时间内只能有一个线程访问)。锁的作用便是将一个公共资源变成一个临界资源。
二,锁的操作
1,锁类型 pthread_mutex_t
pthread_mutex_t 是一个结构体,能够定义一个锁变量。
#endif } __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align; } pthread_mutex_t;
定义锁:
pthread_mutex_t lock;
2,初始化锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);。有两个参数,其中第一个是锁变量的地址,第二个是要初始化成为的值,一般是nullptr.
pthread_mutex_init(&lock, nullptr);
3,上锁
int pthread_mutex_lock(pthread_mutex_t *mutex),能给代码上锁,上锁与解锁之间的这段代码叫做临界区代码(这段代码只允许单线程访问)。
pthread_mutex_lock(&lock);
4,解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex),这个函数能给代码解锁让代码能够被其它的线程访问。
pthread_mutex_unlock(&lock);
5,销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex),这个函数可以把锁变量给摧毁掉。
pthread_mutex_destroy(&lock);
pthread_mutex_t lock;//定义锁
pthread_mutex_init(&lock, nullptr);//初始化锁
pthread_mutex_lock(&lock);//上锁
pthread_mutex_unlock(&lock);//解锁
pthread_mutex_destroy(&lock);//销毁锁
三,线程安全问题演示
现在要演示的便是一个没有加锁的抢票系统,该例子的情形如下:
1,创建四个线程进行前票操作。
2,票为公共资源,抢票直到0为止。
代码如下:
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
using namespace std;
int tickets = 1000;//票的数量
void*Get(void* args)
{
uint64_t i = (uint64_t)args;
string name = "pthread_" + to_string(i);
while(true)
{
if(tickets>0)//票还有时才抢
{
cout << name << " get: " << tickets << endl;
tickets--;
}
else //没了就走
{
break;
}
usleep(1000);
}
}
int main()
{
vector<pthread_t> wait;
for (uint64_t i = 1; i <= 4; i++)
{
pthread_t td;
pthread_create(&td, nullptr, Get, (void *)i);
wait.push_back(td);
usleep(1000);
}
for(auto e:wait)
{
pthread_join(e,nullptr);
}
cout << "main quit....." << endl;
return 0;
}
结果如下:
可以看到这里会出现不同的线程抢到同一张票的情况,这个时候便是出现了线程安全的问题。
解决方式:加入锁
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
using namespace std;
int tickets = 1000;//票的数量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义锁并利用宏来初始化锁
void*Get(void* args)
{
uint64_t i = (uint64_t)args;
string name = "pthread_" + to_string(i);
while(true)
{
pthread_mutex_lock(&lock);//加锁
if (tickets > 0) // 票还有时才抢
{
cout << name << " get: " << tickets << endl;
tickets--;
}
else //没了就走
{
pthread_mutex_unlock(&lock);//解锁
break;
}
pthread_mutex_unlock(&lock);//解锁
usleep(1000);
}
}
int main()
{
vector<pthread_t> wait;
for (uint64_t i = 1; i <= 4; i++)
{
pthread_t td;
pthread_create(&td, nullptr, Get, (void *)i);
wait.push_back(td);
usleep(1000);
}
for(auto e:wait)
{
pthread_join(e,nullptr);
}
pthread_mutex_destroy(&lock);//销毁锁
cout << "main quit....." << endl;
return 0;
}
结果如下:
可以看到加入锁以后这个抢票的代码的结果便变成安全的了。
四,锁的原理
锁又叫做互斥量,当引入这个互斥量以后便可以让一段代码在一段时间内只能被一个线程访问。这是如何做到的呢?先来看一段锁的伪代码:
锁其实就是一个公共资源,这段汇编语句的图示如下:
第一步,将寄存器%al上的数据改为0,mutex(锁)内的值默认是1
第二步,交换%al与mutex上的值。这个时候%al上面的值是1并且%al上的值是线程1上的数据上下文。所以线程1保留了一个值1。
第三步,线程2切换进来,%al上的值被改为0。此时的线程2的值只能是0不能得到锁,所以会阻塞等待。
只有在线程1进行解锁操作时才能将锁归还,紧接着线程2才能得到锁进而访问公共资源
将1换给mutex完成解锁让下一个线程访问这个资源。
所以锁其实就是设置一个变量1在多个线程中流动,一次只能由一个线程得到这个1,只有得到这个1的线程才能访问被锁住的资源。
五,死锁
1,死锁的概念
死锁出现的场景是两个线程在拥有各自的锁的情况下又去申请对方的锁,但是各自又不想释放自己拥有的锁,所以两个线程就会阻塞住不往下执行代码造成死锁。这就如同两个小孩各自有五毛钱,但是一根棒棒糖要一块钱才能买到,所以只有这两个小孩合作才能买到糖,但是各自又不想把自己的五毛钱给对方,所以就会导致大家都吃不到糖。
2,死锁的必要条件
###死锁四个必要条件
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
3,一个线程自己也能造成死锁
一个线程多次申请锁,但是释放的次数与申请的次数不等,这样也会导致死锁问题。所以死锁问题一般都是程序员自己写的bug。