线程互斥
• 共享资源
• 临界资源:多线程执⾏流被保护的共享的资源就叫做临界资源
• 临界区:每个线程内部,访问临界资源的 代码,就叫做临界区
• 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起 保护作⽤
• 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成, 要么未完成
线程共享空间
┌─────────────────────────────┐
│ 内核空间(所有进程共享) │
├─────────────────────────────┤
│ │
│ 共享库 .so/.dll │ ✅ 线程共享(只读代码+全局数据)
│ │
├─────────────────────────────┤
│ 堆 Heap ↑ │ ✅ 线程共享(malloc/new)
│ 向上增长 │
├─────────────────────────────┤
│ │
│ 数据段 .data │ ✅ 线程共享:初始化全局变量、static变量
│ BSS段 .bss │ ✅ 线程共享:未初始化全局、static变量
│ │
├─────────────────────────────┤
│ 代码段 .text │ ✅ 线程共享:只读程序指令
├─────────────────────────────┤
│ 线程1栈 Stack ↓ (私有) │ ❌ 线程私有:局部变量、函数参数
│ 线程2栈 Stack ↓ (私有) │ ❌ 线程私有
│ 线程3栈 Stack ↓ (私有) │ ❌ 线程私有
│ ... │
├─────────────────────────────┤
│ TLS 线程局部存储 │ ❌ 线程私有(__thread / thread_local)
└─────────────────────────────┘
|--------------|--------|------|---|---|---|
| 区域 | 是否线程共享 | 存放内容 | | | |
| .text 代码段 | ✅ 共享 | 程序二进制指令,只读 ||| |
| .data / .bss | ✅ 共享 | 全局变量、static 静态变量 ||| |
| Heap 堆 | ✅ 共享 | malloc、new 动态分配内存 ||| |
| 共享库 | ✅ 共享 | 动态库代码和全局变量 ||| |
| 线程栈 Stack | ❌ 私有 | 普通局部变量、函数参数、返回地址 ||||
| TLS | ❌ 私有 | __thread / thread_local 变量 ||| |
| 寄存器 | ❌ 私有 | 线程上下文,切换时保存恢复 ||| |
互斥量mutex(锁)
作用:
保护共享资源,保证临界区代码同一时间只有一个线程执行,解决竞态条件(race condition)
同一个进程多个线程共享全局变量、堆内存。 像 cnt++ 不是一条 CPU 指令,是三步:
- 把 cnt 从内存读到寄存器
- 寄存器 + 1
- 写回内存
线程切换插在中间,就会出现数据丢失、结果错乱 ,这就是竞态。 锁就是用来把这一组操作打包成原子操作。

cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
int count = 100;
void *route(void *argc)
{
char *id = (char*)argc;
while(1)
{
if(count > 0)
{
usleep(1000);
printf("%s sells ticket: %d\n", id, count);
count--;
}
else
{
break;
}
}
return nullptr;
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,route,(void*)"thread 1");
pthread_create(&t2,NULL,route,(void*)"thread 2");
pthread_create(&t3,NULL,route,(void*)"thread 3");
pthread_create(&t4,NULL,route,(void*)"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
return 0;
}

互斥量(锁)的接口
静态初始化
cpp
pthread_mutex_t mutex;
// 静态初始化(推荐全局/静态锁)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态分配
cpp
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr)
- mutex:锁变量地址
- attr:锁属性,
NULL= 默认属性 - 返回值:成功返回 0,失败返回错误码
销毁锁
cpp
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 用完释放资源;销毁前必须保证没有线程持有这个锁
- 静态初始化的锁,程序退出前也建议 destroy
加锁
cpp
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 锁空闲:直接拿到锁,返回
- 锁已经被别的线程持有:阻塞休眠,直到锁释放
⚠️ 同一个线程重复 lock 默认会死锁(普通锁)
解锁
cpp
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 释放锁,唤醒等待这个锁的线程
- 谁加锁,尽量谁解锁;不能解锁一个没持有的锁(未定义行为)
非阻塞加锁
cpp
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 锁空闲:拿到锁,返回 0
- 锁被占用:不阻塞,直接返回 EBUSY 适合不想卡住线程的场景,轮询尝试加锁
限时阻塞加锁
cpp
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
- 阻塞等待,直到拿到锁 或者 到达设置的绝对时间超时
- 超时返回
ETIMEDOUT,防止永久死锁

cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
int count = 100;
pthread_mutex_t mutex;
void *route(void *argc)
{
char *id = (char*)argc;
while(1)
{
pthread_mutex_lock(&mutex);
if(count > 0)
{
usleep(1000);
printf("%s sells ticket: %d\n", id, count);
count--;\
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
return nullptr;
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,route,(void*)"thread 1");
pthread_create(&t2,NULL,route,(void*)"thread 2");
pthread_create(&t3,NULL,route,(void*)"thread 3");
pthread_create(&t4,NULL,route,(void*)"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
return 0;
}
锁的封装
cpp
#pragma once
#include<iostream>
#include<pthread.h>
namespace LockModule
{
class Mutex
{
public:
Mutex(const Mutex&) = delete;
const Mutex& operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock, nullptr);
(void)n;
}
~Mutex()
{
int n = ::pthread_mutex_destroy(&_lock);
(void)n;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
(void)n;
}
pthread_mutex_t *LockPtr()
{
return &_lock;
}
void Unlock()
{
int n = ::pthread_mutex_unlock(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
线程同步
条件变量
当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列 中。这种情况就需要⽤到条件变量
同步概念与竞态条件
• 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免 饥饿问题,叫做同步
• 竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也 不难理解
条件变量接口
定义
cpp
// 静态初始化(全局/静态)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
销毁
cpp
int pthread_cond_destroy(pthread_cond_t *cond);
等待
cpp
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
- 自动解锁 mutex
- 线程阻塞,放到条件变量等待队列,等待唤醒
- 收到唤醒信号后,自动重新加锁 mutex,函数返回,继续往下执行
⚠️ 必须在已经持有 mutex 的情况下调用! ⚠️ 必须用 while 循环判断条件,不能用 if!(存在虚假唤醒 spurious wakeup)
pthread_cond_wait 用于阻塞等待条件信号;调用时先释放互斥锁让别的线程可以抢到锁、修改条件,收到唤醒信号后,自动重新获取互斥锁再返回;必须配合 mutex,并且要用 while 循环处理虚假唤醒。
唤醒等待线程
cpp
//唤醒一个等待线程
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒全部等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);

cpp
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *active(void *argc)
{
std::string name = static_cast<const char *>(argc);
while(1)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("%s is active!\n", name.c_str());
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, nullptr, active, (void*)"thread-1");
pthread_create(&tid2, nullptr, active, (void*)"thread-2");
pthread_create(&tid3, nullptr, active, (void*)"thread-3");
sleep(1);
while(true)
{
printf("main wakeup thread...\n");
//pthread_cond_signal(&cond);//唤醒单线程
pthread_cond_broadcast(&cond);//唤醒多线程
sleep(1);
}
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
}
条件变量封装
cpp
#pragma once
#include<iostream>
#include<pthread.h>
#include"Mutex.hpp"
namespace CondModule
{
using namespace LockModule;
class Cond
{
public:
Cond()
{
int n = ::pthread_cond_init(&_cond, nullptr);
(void)n;
}
void Wait(Mutex &mutex)
{
int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());
}
void Notify()
{
int n = ::pthread_cond_signal(&_cond);
(void)n;
}
void NotifyAll()
{
int n = ::pthread_cond_broadcast(&_cond);
}
~Cond()
{
int n = ::pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
生产消费模型
核心:
生产者负责往缓冲区放数据,消费者从缓冲区拿数据;缓冲区满,生产者阻塞;缓冲区空,消费者阻塞。

cpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include "Mutex.hpp"
#include "Cond.hpp"
namespace BlockQueueModule
{
using namespace LockModule;
using namespace CondModule;
static const int gcap = 10;
template <typename T>
class BlockQueue
{
private:
bool IsFull()
{
return _q.size() == _cap;
}
bool IsEmpty()
{
return _q.empty();
}
public:
BlockQueue(int cap = gcap) : _cap(cap), _cwait_num(0), _pwait_num(0)
{
}
void Equeue(const T &in)
{
LockGuard lockguard(_mutex);
while (IsFull())
{
std::cout << "生产者进入等待..." << std::endl;
_pwait_num++;
_productor_cond.Wait(_mutex);
_pwait_num--;
std::cout << "生产者被唤醒..." << std::endl;
}
_q.push(in);
if (_cwait_num)
{
std::cout << "唤醒消费者..." << std::endl;
_consumer_cond.Notify();
}
}
void Pop(T *out)
{
LockGuard lockguard(_mutex);
while (IsEmpty())
{
std::cout << "消费者进入等待..." << std::endl;
_cwait_num++;
_consumer_cond.Wait(_mutex);
_cwait_num--;
std::cout << "消费者被唤醒.." << std::endl;
}
*out = _q.front();
_q.pop();
if (_pwait_num)
{
std::cout << "唤醒生产者..." << std::endl;
_productor_cond.Notify();
}
}
~BlockQueue()
{
}
private:
std::queue<T> _q; // 保护数据的容器,临界资源
int _cap; // bq的最大容量
Mutex _mutex; // 互斥
Cond _productor_cond; // 生产者条件变量
Cond _consumer_cond; // 消费者条件变量
int _cwait_num;
int _pwait_num;
};
}
POSIX信号量
信号量本质:一个计数器 + 等待队列,可以用来做【互斥】,也可以做【同步(线程顺序控制)】 互斥锁(mutex)只能 0/1;信号量计数器可以是大于 1 的数值
信号量接口
初始化信号量
cpp
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量变量pshared:- 0:线程间共享(同进程内线程),最常用
- 非 0:进程间共享(放在共享内存)
value:信号量初始值- 返回 0 成功
注意:信号量没有静态初始化方式,必须 sem_init
等待
cpp
int sem_wait(sem_t *sem);
- 计数器 > 0:计数器 -=1,直接返回
- 计数器 == 0:阻塞等待 ,直到别的线程
sem_post唤醒它
等价于:拿资源,拿不到就等
发布
cpp
int sem_post(sem_t *sem);
- 计数器 +=1
- 如果有线程正在 sem_wait 阻塞,会唤醒一个等待的线程
等价于:归还资源
销毁
cs
int sem_destroy(sem_t *sem);
线程安全
导致不安全原因
核心:多个线程,并发访问同一个共享资源,并且至少有一个线程是写操作,同时没有同步保护,就会线程不安全。 三个必要条件(同时满足才会不安全):
- 共享资源:多个线程访问同一个变量 / 对象(全局变量、静态成员、堆上同一个对象)
- 并发修改:至少一个线程对资源做写(修改)操作;只读不会有问题
- 缺少同步机制:没有锁、原子变量等手段保护临界区
具体拆解原因
- 指令不是原子的 比如
inst = new Singleton()、cnt++,不是一条 CPU 指令,会被拆成多步。线程调度器可以在中间切走线程,造成数据错乱。new分为三步:分配内存 → 指针赋值 → 调用构造函数。 CPU / 编译器还可能指令重排,打乱执行顺序。 - 内存可见性问题 每个 CPU 有缓存,线程修改的变量可能只留在 CPU 缓存,没有刷回主存;其他线程读到旧值。
- 指令重排 编译器、CPU 为了性能优化,会调整代码执行顺序,保证单线程逻辑不变,但多线程下逻辑错乱。
一句话:多线程并发读写共享资源,缺少同步保护 → 线程不安全。
单例模式
饿汉VS懒汉
- 饿汉:程序启动直接创建实例,天然线程安全;缺点:程序一开始就占用资源,不能延迟加载
- 懒汉 :第一次调用
GetInstance()才创建实例,按需加载;默认不安全,需要加手段保证线程安全
饿汉式
cpp
class Singleton {
public:
static Singleton* GetInstance() {
return &inst_;
}
private:
// 私有化构造、拷贝、赋值,防止外部创建
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton inst_; // 全局静态变量,main之前就构造好
};
Singleton Singleton::inst_;
- 实例在 main 函数之前,静态初始化阶段就完成构造
- 此时还没有任何用户线程启动,不存在并发竞争 ✅ 安全原理:初始化发生在多线程运行之前,没有并发访问 缺点:不是懒加载
懒汉式
双重检查锁定 DCL(Double-Check Locking,面试最常问)
cpp
#include <mutex>
class Singleton {
public:
static Singleton* GetInstance() {
if (inst_ == nullptr) { // 第一次检查:已经创建就不抢锁(提升性能)
std::lock_guard<std::mutex> lock(mtx_);
if (inst_ == nullptr) { // 第二次检查:防止多个线程同时闯过第一层if
inst_ = new Singleton();
}
}
return inst_;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::mutex mtx_;
static Singleton* volatile inst_;
};
std::mutex Singleton::mtx_;
Singleton* volatile Singleton::inst_ = nullptr;
⚠️ 为什么要两层 if?
- 第一层 if:避免每次调用都加锁,减少锁竞争,提高效率
- 第二层 if:多个线程同时通过第一层 if,阻塞在锁上;一个线程创建完释放锁,后面拿到锁的线程要再次判断,避免重复创建
⚠️ volatile 的作用(C++11 前)
new Singleton() 编译器 / CPU 可能重排为三步:
- 分配内存
- 把 inst_指针指向这块内存
- 调用构造函数初始化对象
重排后:先赋值指针,后构造 。别的线程看到 inst_不为空,但对象还没构造完成,拿到半成品对象! volatile 阻止编译器优化、禁止指令重排。
✅ C++11 及以后,内存模型规范,
std::atomic也可以替代 volatile。
:C++11 静态局部变量懒汉式
cpp
class Singleton {
public:
static Singleton& GetInstance() {
// 静态局部变量:第一次进入函数时才初始化
// C++11标准保证:静态局部变量初始化是线程安全的!
static Singleton inst;
return inst;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- 写法极简,自动线程安全(C++11 标准规定)
- 懒加载,第一次调用才创建
- 不用手动写锁、不用 volatile
- 栈静态变量,程序退出自动析构,不会内存泄漏
- 不能控制析构时机(程序退出才析构)
- 如果单例构造抛出异常,有问题