c++oding="utf-8" ?>
std::filesystem::permissions 多线程裸用会触发未定义行为或崩溃,因其底层 chmod/setfileattributes 非原子、非线程安全,且标准要求用户对同一路径的修改操作手动同步。

多线程下直接调用 std::filesystem::permissions 修改同一文件权限,大概率触发未定义行为或运行时崩溃——因为该函数底层依赖系统调用(如 chmod),而 POSIX 和 Windows 均不保证该操作的原子性或线程安全性。
为什么 permissions 不能在多线程中裸用
标准明确要求:所有 std::filesystem 函数对同一路径的并发调用,若涉及修改(如 permissions、remove、rename),必须由用户手动同步。原因包括:
-
permissions在 Linux 上本质是chmod()系统调用,在 Windows 上映射为SetFileAttributes(),二者均非可重入,也不做内部锁 - 多个线程同时修改同一文件权限,可能造成中间态丢失(例如 A 线程加
owner_exec,B 线程删group_read,最终结果取决于执行顺序) - 某些 libc 实现(如旧版 libstdc++)甚至会在内部缓存路径状态,多线程读写该缓存会引发数据竞争
正确做法:用互斥量保护路径级操作
不是保护“整个文件系统”,而是按「目标路径」粒度加锁。每个唯一 fs::path 对应一个互斥量,避免不同文件间不必要的串行化。
示例结构:
std::mutex& get_path_mutex(const std::filesystem::path& p) {
static std::unordered_map<:filesystem::path std::mutex> mutex_map;
static std::mutex map_mutex;
std::lock_guard<:mutex> lk(map_mutex);
return mutex_map[p];
}
<p>void safe_set_permissions(const std::filesystem::path& p, std::filesystem::perms perms, std::filesystem::perm_options opts) {
std::lock_guard<:mutex> lk(get_path_mutex(p));
std::filesystem::permissions(p, perms, opts);
}
</:mutex></p></:mutex></:filesystem::path>
- 不要用全局单互斥量,否则所有文件权限操作全被串行化,性能归零
- 注意
std::filesystem::path的相等性:确保传入的是规范路径(用p.lexically_normal()预处理),否则a/b和a/../a/b会被视为两个键 - 若路径来自用户输入或拼接,务必先检查是否存在(
exists(p)),避免对不存在路径反复加锁再失败
更轻量的替代方案:改用原子式 chmod(仅限 Unix/Linux)
如果只在类 Unix 平台运行,且只需设置完整权限(非增量 add/remove),可绕过 std::filesystem,直接调用系统 API 并配合 std::atomic_flag 做快速路径锁:
#include <sys>
#include <atomic><p>static std::atomic_flag chmod_lock = ATOMIC_FLAG_INIT;</p>
<p>bool atomic_chmod(const char* path, mode_t mode) {
while (chmod_lock.test_and_set(std::memory_order_acquire)) {
// 自旋等待,适合短操作
}
int ret = chmod(path, mode);
chmod_lock.clear(std::memory_order_release);
return ret == 0;
}
</p></atomic></sys>
- 比
std::mutex开销更低,但仅适用于单次chmod调用,不支持perm_options::add等逻辑 - 无法跨平台;Windows 下需换用
SetFileAttributes()+ Critical Section - 仍需确保
path是 C 字符串且生命周期足够长(不能传临时string.c_str())
容易忽略的边界:符号链接与挂载点
当目标路径是符号链接时,std::filesystem::permissions(p, ...) 默认修改的是链接**指向的目标文件**权限(POSIX 行为),而非链接自身。这在多线程中极易引发意外交互:
- 线程 A 修改
./link权限 → 实际改了/real/file.txt - 线程 B 同时修改
/real/file.txt权限 → 两次修改冲突 - 若链接指向网络挂载点(如 NFS),
chmod可能阻塞数秒,导致锁持有时间远超预期
解决方法:显式判断是否为符号链接,统一处理策略(如禁止对 symlink 调用 permissions,或改用 lchmod(Linux)/ SetFileAttributes(Windows)操作链接本身)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











