这一篇,我们主要来总结一下Linux与C++下线程锁的接口!
互斥量接口:
初始化互斥量
初始化互斥量有两种方法
方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER方法2,动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数: mutex:要初始化的互斥量 attr:NULL
销毁:
注意:
使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁
条件变量接口:

初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁:
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
唤醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond)
成功返回0


上面是Linux的接口,现在,我们顺便来引出来
C++的接口:
实际上,Linux与C++的接口都是差不多的,只不过是C++封装成类罢了,所以我们了解完Linux的锁后,类似地类比到C++那也是能看懂的!
std::mutex
std::mutex最基础的互斥量,支持「加锁-解锁」的基本操作,不可递归加锁(同一线程重复加锁会导致死锁)。
接口函数 功能说明 lock() 阻塞线程,直到获取互斥量所有权(若已被其他线程锁定,则当前线程等待)。 unlock() 释放互斥量所有权(必须由当前持有锁的线程调用,否则行为未定义)。 try_lock() 尝试非阻塞获取锁:成功返回,失败(锁已被占用)返回,不阻塞。 C++中出了一个智能锁:它避免手动解锁(通常我们写C++时推荐使用)
std::mutex 需手动调用 lock() 和 unlock() ,容易因异常或忘记解锁导致死锁。C++提供RAII风格的智能锁,自动管理锁的生命周期(构造时加锁,析构时解锁)。
std::lock_guard(基础智能锁)


#include <mutex>
#include <thread>
int shared_data = 0;
class LogMutex
{
public:
void lock()
{
std::cout<<"lock被调用"<<std::endl;
mtx.lock();
}
void unlock()
{
std::cout<<"unlock被调用"<<std::endl;
mtx.unlock();
}
private:
std::mutex mtx;
};
int main()
{
LogMutex log_mtx;
std::lock_guard<LogMutex> lock(log_mtx);
std::cout<<"lock_guard已经创建"<<std::endl;
std::cout<<"执行临界区代码"<<std::endl;
shared_data++;
std::cout<<"作用域结束"<<std::endl;
shared_data++;
return 0;
}

当出现异常时:
int main()
{
LogMutex log_mtx;
try{
{
std::lock_guard<LogMutex> lock(log_mtx);
std::cout << "lock_guard已经创建" << std::endl;
std::cout << "执行临界区代码" << std::endl;
throw std::runtime_error("故意抛出异常");
shared_data++;
std::cout << "作用域结束" << std::endl;
shared_data++;
}
}catch(const std::exception&e)
{
std::cout<<"捕捉到异常:"<<e.what()<<std::endl;
}
return 0;
}

注意:

如果是这样写的话,这次 unlock 没被调用,是因为C++异常处理的“栈展开”行为,在程序 terminate 时可能被跳过了——但这是环境/编译器的“极端情况”,我们可以通过捕获异常来确保栈展开(让 lock_guard 析构)。
问题本质:
当你直接抛出未捕获的异常时,程序会调用 std::terminate 终止运行。而部分编译器/环境下, std::terminate 会直接终止进程,不会执行“栈展开”(即不调用局部对象的析构函数),所以 lock_guard 的析构(及 unlock )没机会执行。
所以我们才使用到了上面的捕捉异常,强制触发栈展开!
同时为什么最好要给lock_guard加局部作用域呢?
因为局部作用域结束,即使抛异常也会调到析构函数的!
std::unique_lock(灵活智能锁)


- 用途:比 lock_guard 更灵活,支持手动加锁/解锁、延迟加锁、条件变量配合等场景。
- 构造时可指定 std::defer_lock :延迟加锁(需后续手动调用 lock() )。
- lock() / unlock() :手动控制锁的获取与释放。
- try_lock() :非阻塞尝试加锁。
- owns_lock() :判断当前智能锁是否持有互斥量所有权。std::mutex mtx; int shared_data=0; int main() { std::unique_lock<std::mutex> lock(mtx,std::defer_lock); lock.lock(); shared_data++; lock.unlock(); return 0; }
std::recursive_mutex(递归互斥量)


- 特点:允许同一线程多次加锁(需对应次数的解锁),避免递归函数中的死锁。
- 接口:与 std::mutex 一致( lock() / unlock() / try_lock() ),可搭配 lock_guard 或 unique_lock 使用。
- 注意:仅在「递归调用必须访问同一临界区」时使用,避免滥用(可能隐藏设计问题)。
std::timed_mutex(超时互斥量)


- 特点:在 std::mutex 基础上,支持「超时等待锁」(避免无限阻塞)。
- 新增接口:
- try_lock_for(std::chrono::duration) :等待指定时间,超时返回 false 。
- try_lock_until(std::chrono::time_point) :等待到指定时间点,超时返回 false 。
注意:
1. 避免死锁:
- 同一线程对 std::mutex 不可重复 lock() (改用 recursive_mutex )。
- 多线程加锁时,严格遵守「相同的加锁顺序」(如线程1先锁A再锁B,线程2也必须先A后B)。
2. 智能锁优先:除非有特殊需求(如手动控制锁的生命周期),否则优先使用 lock_guard (简单场景)或 unique_lock (复杂场景),避免手动调用 unlock() 。
3. 互斥量不可拷贝/移动:所有互斥量类( mutex / recursive_mutex 等)均禁用拷贝和移动构造,确保锁的唯一性。
C++线程接口:

在C++11及以后标准中,线程操作的核心接口是 <thread> 头文件中的 std::thread 类,它提供了创建、管理线程的基础功能
| 接口函数 | 功能描述 |
| std::thread t(func) | 构造函数:创建线程对象 t,并执行函数func ( 可带参数,需在构造时传入)。 |
| t.join() | 阻塞当前线程,等待线程 t 执行完毕后再继续(避免主线程先退出导致子线程被终止)。 |
| t.detach() | 将线程 t 与主线程“分离”,线程 t成为后台线程,其资源由系统自动回收(分离后不可再join )。 |
| t.joinable() | 判断线程 t是否可被 join(未被join /detach、未移动构造的线程返回true )。 |
| std::thread::hardware_concurrency | 静态成员函数:返回当前设备的逻辑核心数(如4核8线程CPU返回8),用于指导线程数量设计。 |
| std::this_thread::get_id() | 获取当前线程的唯一标识(返回 std::thread::id 类型,可直接打印); |
| std::this_thread::sleep_for(duration) | 让当前线程休眠指定时间(如 chrono::seconds(1) 表示1秒); |
| std::this_thread::yield() : |
当前线程主动“让出”CPU,允许其他线程优先执行(适用于减少忙等消耗)。 ps:Linux中是sched_yield() |
现在,我们来用代码练习一下:
无参函数:
void mythread()
{
std::cout<<"hello mythread"<<std::endl;
}
int main()
{
std::thread t(mythread);
if(t.joinable())
{
t.join();
}
std::thread::id th_id=std::this_thread::get_id();
std::cout<<th_id<<std::endl;
std::cout<<std::this_thread::get_id()<<std::endl;
return 0;
}

有参函数:(值传递/引用传递)
- 值传递:默认情况下,线程会拷贝参数(即使函数形参是引用);
- 引用传递:需用 std::ref() 包裹实参,强制传递引用(注意确保实参生命周期长于线程)。void mythread(int &a,int b) { a+=b; std::cout<<"mthread: a="<<a<<std::endl; } int main() { int num=10; std::thread t(mythread,num,5); if(t.joinable()) { t.join(); } std::cout<<"main thread:num"<<num<<std::endl; return 0; }
void mythread(int &a,int b) { a+=b; std::cout<<"mthread: a="<<a<<std::endl; } int main() { int num=10; std::thread t(mythread,std::ref(num),5); if(t.joinable()) { t.join(); } std::cout<<"main thread:num"<<num<<std::endl; return 0; }
上面我们可以看到,当我们传递引用的时候,需要用std::ref(num),否则num会会被拷贝,导致出现报错
分离线程(后台线程)
void Background()
{
for (int i = 0; i < 5; i++)
{
std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠1s
std::cout << "Background thread running" << std::endl;
}
}
int main()
{
std::thread t(Background);
t.detach(); // 分离线程,主线程无需等待
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "main thread quit" << std::endl;
return 0;
}

注意:
1. 线程对象销毁前未 join / detach
若线程对象(如 std::thread t )在生命周期结束时仍处于“可 join”状态(未调用 join 或 detach ),会触发 std::terminate() 终止程序。解决:必须在对象销毁前调用 join 或 detach 。
2. 分离线程访问局部变量
分离线程( detach 后)的生命周期可能长于主线程的局部变量,若线程内访问该变量,会导致野指针/悬垂引用。解决:仅让分离线程访问全局变量、静态变量或动态分配的内存(需确保内存不提前释放)。
3. 重复 join 同一线程
一个线程只能被 join 一次,重复 join 会触发未定义行为。解决:每次 join 前用 t.joinable() 检查。
好了,关于接口介绍暂时到此结束了。
需要了解锁的具体原理,可移到接下来我要更新的文章中,这篇文章也是基于下面的文章写的!
希望大家一起进步!
最后,到了本次鸡汤环节:
不要等待机会,而是创造机会!









17万+

被折叠的 条评论
为什么被折叠?



