Linux中的锁

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线程同步

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情 况就需要用到条件变量。

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值