BUG笔记:双检查锁解决多线程不安全

【问题背景】

项目中出现了挂死问题,gdb查看,表明是A函数中出现了内存越界错误,其中该A函数是被多个线程所调用,并且程序并不是立刻挂死,现象不易复现。

【问题原因】

该函数使用共享队列记录和存储数据,多个线程读取该队列获取数据。

其中一部分的代码为:

int sendTB(Myqueue * queue)
{
    if(NULL == queue)
        return -3;
    if(queue->index >= queue->length )
        return -1;
    if(shared_mutex_trylock(queue))
    {
        return -2;
    }
        /*************存数据的一系列操作************/
        queue->data[queue->index] = 10;
    queue->index ++;
    shared_mutex_unlock(queue);
    return 0;
}

很显然这部分的代码的临界资源是共享队列queue,于是在临界资源的前后进行了加锁和解锁的机制,可悲剧还是发生了。

这部分代码乍一看没有什么问题,可实际上在多线程的环境下,依旧会出现问题,其原因在于,先判断了越界的情况后进行加解锁操作,这样会导致,任务在被锁阻塞之前,队列确实没有满,但是在进入临界区的时候,由于之前处理了任务,队列已经满容量,等本次任务执行的时候也没有一个判断就直接越界了。

【举个例子】

假设该共享队列的大小为5,其中已经被占用了4个,现在有两个线程同时都执行到检查越界的判断上(line:5),此时两个任务会抢着剩余的1个位置,却在加锁之前又都有竞争的条件(即:队列未满)这样就导致两个任务都会执行后续的代码,而其中一个任务抢到了cpu,优先执行了存数据的操作,使用了最后一个容量,解锁后,另一个任务还是执行同样的操作,这时候就会出现内存越界的现象。

【如何防止】

只要在加锁的部分再添加一个越界判断就可以:

int sendTB(Myqueue * queue)
{
    if(NULL == queue)
        return -3;
    if(queue->index >= queue->length )//第一次判断
        return -1;
    if(shared_mutex_trylock(queue))
    {
        return -2;
    }
        if(queue->index >= queue->length )//第二次判断
            return -1;
        /*************存数据的一系列操作************/
        queue->data[queue->index] = 10;
    queue->index ++;
    shared_mutex_unlock(queue);
    return 0;
}

【引发疑问】

第一次的判断可以删掉吗?

理论上来说,这样并不会造成线程不安全问题,但是这样会导致即使队列处于满的状态,但每一个进入到该函数的任务都会被阻塞掉,加锁解锁变得更加的频繁,影响性能,所以还是带着第一次判断更好

在这样的基础之上如何更加的优化性能?

我个人认为,锁的粒度可以更加的细化,不是对整个队列进行加锁,而是对queue->data[i]加锁,这样在单个元素被占用的情况下,也不会阻塞其他元素的存取过程,从而提高性能。但是这样会需要额外的维护一个队列来记录哪个元素处于空闲状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值