C++线程编程:循环尝试获取互斥锁的代码是否有效?
嘿,作为C++线程编程新手,你能想到用shared_mutex来区分OpenGL库的读写场景(绘图读VAO、增删写VAO),这点思路非常对——读写锁本来就是为这类读多写少的并发场景设计的,能大幅提升并发效率。不过你的这段代码确实存在冗余和可优化的地方,咱们来仔细拆解:
先说说你现有代码的问题
unique_lock<shared_mutex> lock(someSharedMutex); if (!lock.owns_lock()) { while (!lock.try_lock()) {} } // 修改数据
这里最关键的误解是:unique_lock<shared_mutex> lock(someSharedMutex); 这个构造函数默认会调用lock()方法,也就是阻塞当前线程直到成功获取排他锁。所以lock.owns_lock()在这里几乎永远是true,后面的if判断和自旋循环完全是多余的,根本不会被执行。你觉得代码能正常运行,其实是因为构造函数已经帮你完成了加锁的核心工作。
退一步说,假设你本来想构造一个不自动加锁的unique_lock(比如用defer_lock参数),那while (!lock.try_lock()) {}这种自旋等待的方式也不推荐——它会让线程一直占用CPU空转,在高并发场景下会严重浪费系统资源,远不如让操作系统把线程挂起,直到锁可用时再唤醒(也就是lock()的默认行为)高效。
适合你场景的规范写法
结合你的OpenGL库需求:绘图线程用共享锁(允许多个读线程同时访问VAO列表),VAO增删操作用排他锁(同一时间只能有一个写线程修改列表),正确的代码应该是这样的:
写操作(VAO添加/移除)
void addVAO(const VAO& new_vao) { // 构造unique_lock时自动获取排他锁,阻塞直到拿到锁 unique_lock<shared_mutex> lock(someSharedMutex); // 安全修改VAO存储vector vao_storage.push_back(new_vao); } void removeVAO(size_t index) { unique_lock<shared_mutex> lock(someSharedMutex); if (index < vao_storage.size()) { vao_storage.erase(vao_storage.begin() + index); } }
读操作(绘图)
void drawAllVAOs() { // 用shared_lock获取共享锁,多个读线程可同时持有 shared_lock<shared_mutex> lock(someSharedMutex); for (const auto& vao : vao_storage) { vao.draw(); } }
特殊场景的非阻塞加锁(如果需要)
如果你确实有需求不想让线程阻塞(比如要在等待锁的间隙做其他操作),可以用defer_lock参数构造unique_lock,然后配合try_lock()或者带超时的try_for()/try_until(),但绝对不要用自旋循环:
bool tryAddVAO(const VAO& new_vao) { unique_lock<shared_mutex> lock(someSharedMutex, defer_lock); if (lock.try_lock()) { vao_storage.push_back(new_vao); return true; } // 没拿到锁,返回失败或者做其他处理 return false; }
总结
你的代码虽然能运行,但存在冗余逻辑和潜在的效率问题。直接用unique_lock的默认构造(自动加排他锁)是最规范、高效的方式,完全适配你VAO增删的写操作场景;而绘图读操作则用shared_lock获取共享锁,最大化并发能力。
内容的提问来源于stack exchange,提问作者WaffleMan0310

