【Linux】线程互斥

[复制链接]
发表于 2026-10-4 16:18:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

线程大部分时候用的是自己栈上的局部变量,别的线程碰不到。但总有些数据要在线程之间流转,这种变量叫共享变量。一旦多个线程同时读写同一个共享变量,问题就来了——这篇用一个抢票的例子把互斥这件事讲清楚。

一、为什么需要互斥

局部变量放在线程栈里,天然归一个线程所有;共享变量放在全局或堆上,所有线程都够得着。线程之间靠共享变量实现交互的同时,也把竞态风险一起引进来了。

用一个全局变量 ticket 表示余票,起多个线程同时抢。以 5 个线程、100 张票为例,核心逻辑是这样:

#define NUM 5

int ticket = 100;

class threadData
{
public:
    threadData(int number)
    {
        threadname = "thread-" + to_string(number);
    }

public:
    string threadname;
};

void* getTicket(void* args)
{
    threadData* td = static_cast<threadData*>(args);

    while (1)
    {
        if (ticket > 0)
        {
            usleep(1000);   // 模拟抢票耗时
            printf("%s is running, get a ticket: %d\n", td->threadname.c_str(), ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
    return nullptr;
}

二、问题出在哪一行

危险的是 if (ticket > 0) 与 ticket-- 这两句不是原子的。实际执行时是「读 ticket → 判断 → 自减 → 写回」,中间完全可能被别的线程插进来:

  • 线程 A 读到 ticket=1,判断通过;
  • 还没来得及自减,线程 B 也读到 ticket=1,同样判断通过;
  • A 写回 0,B 也写回 0;
  • 两张票被两个线程当成一张卖掉了。

再叠加 usleep 这种主动让出 CPU 的调用,窗口被放得更大——现象就是票数会出现重复编号,或者卖到 0 之后仍有人抢到。

三、互斥量的解法

办法很直接:把「判断 + 修改」整段包进互斥锁,同一时刻只允许一个线程进入。每个线程持自己的锁指针:

void* getTicket(void* args)
{
    threadData* td = static_cast<threadData*>(args);
    pthread_mutex_t* lock = td->_lock;

    while (1)
    {
        pthread_mutex_lock(lock);

        if (ticket > 0)
        {
            usleep(1000);
            printf("%s is running, get a ticket: %d\n", td->_threadname.c_str(), ticket);
            ticket--;
        }
        else
        {
            pthread_mutex_unlock(lock);
            break;
        }

        pthread_mutex_unlock(lock);
    }
    return nullptr;
}

四、写mutex代码的四条硬规则

  • 临界区要小:锁里面只放必要的判断和自增自减,printf、耗时的_io 尽量挪到锁外,否则并发度会被锁打死。
  • 每个出口都要解锁:循环里 break 那条分支容易漏,一漏就是死锁全局线程。
  • 用 RAII 或 try/finally 兜底:C++ 里更推荐 std::lock_guard<std::mutex>,作用域结束自动解锁,从根本上杜绝漏解。
  • 别在持锁时再申请另一把锁:多把锁很容易出现 A 等 B、B 等 A 的死锁。

五、和信号量的区别,一句话

互斥量强调「谁进来谁出去」的排他性,适合保护临界资源;信号量强调「同时允许多少个」的计数,适合限流。抢票这种同一份数据只能一个人改的场景,用互斥。

结论

共享变量是线程通信的桥,也是竞态的源。判断一个程序有没有互斥问题,就看有没有「先判断后修改」且中间可能被打断的结构。修法恒定:把判断加修改整段锁起来,锁内只做最小必要操作,所有退出路径都要解锁——C++ 里直接用 lock_guard。

qidao123.com企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表