不会。os.MkdirAll对已存在路径返回nil而非os.ErrExist,其内部依赖系统mkdir(2)的EEXIST原语保障并发安全,多个goroutine同时调用不会因竞态报“file exists”错误,但首次创建者的perm决定目录权限,且不处理symlink循环。

os.MkdirAll并发调用会报“file exists”吗?
不会。这是最常被误解的一点:os.MkdirAll 对已存在路径返回 nil,不是 os.ErrExist;它内部做了原子性检查,多个 goroutine 同时调用 os.MkdirAll("a/b/c", 0755) 不会因竞态报错,也不会重复创建。
但要注意:它只保证“最终目录存在”,不保证中间目录的创建顺序或权限一致性——比如两个 goroutine 同时创建 "x/y" 和 "x/z","x" 可能被任一调用创建,权限取决于首个成功者的 perm 参数(后续调用不改权限)。
- 真正可能出问题的是你自己的逻辑:比如在
os.MkdirAll后立即os.OpenFile(..., os.O_CREATE),此时若另一 goroutine 刚删了目录,就可能失败 -
os.MkdirAll本身无锁、无全局状态,纯靠系统级mkdir(2)的 EEXIST 原语保障,所以高并发下安全 - 别在循环里反复调用
os.MkdirAll—— 它已幂等,重复调用只是浪费系统调用
为什么并发创建后某些子目录权限不对?
因为 os.MkdirAll 的 perm 参数只用于**首次创建该目录时**,且 Linux/macOS 下受当前进程 umask 截断。并发场景下,谁先创建 "a","a" 的权限就由谁传的 perm 决定;后到者发现存在,直接跳过,不重设权限。
例如:goroutine A 用 0755 创建 "logs",goroutine B 用 0700 同时尝试创建同一目录,B 不会失败,但 "logs" 权限仍是 A 设的 0755(实际可能是 0755 &^ umask)。
立即学习“go语言免费学习笔记(深入)”;
- 解决方案:如需统一权限,创建后显式
os.Chmod(path, 0755),但注意避免对非目标目录误操作 - 更稳妥的做法是先
os.Stat检查目录是否存在且权限符合预期,再决定是否Chmod - Windows 上可忽略此问题——权限位基本无效
并发创建 + symlink 路径时容易卡死或报 ELOOP
os.MkdirAll 会自动解析符号链接,如果路径中含循环 symlink(如 a → b, b → a),它会在内核层报 syscall.ELOOP,Go 将其转为 *os.PathError,错误信息类似 "mkdir /path: too many levels of symbolic links"。
并发加剧这个问题:多个 goroutine 同时尝试解析同一坏链,可能触发更多系统调用,甚至拖慢整个程序。
- 预防手段:创建前用
filepath.EvalSymlinks预检路径,捕获syscall.ELOOP并提前报错 - 不要依赖
os.MkdirAll自动处理 symlink 循环——它不跳过,也不重试,直接失败 - 若路径来自用户输入,必须校验:先
EvalSymlinks,再确认结果不在禁止挂载点(如/proc、只读文件系统)
如何安全地并发初始化一批目录?
别让每个 goroutine 独立调用 os.MkdirAll 处理完整路径;而是拆解路径组件,分层控制并发粒度。例如要创建 data/2024/06/15、data/2024/06/16、data/2024/07/01,应:
- 先并发创建
data/2024(共用父级,一次搞定) - 再按月分组,并发创建
data/2024/06、data/2024/07 - 最后按日创建叶子目录
这样既减少系统调用总量,又避免同一父目录被高频争抢。关键点是:用 filepath.Dir 提取公共前缀,用 map[string]struct{} 去重,再批量处理。
真正容易被忽略的,是权限与 symlink 的组合问题——它们在单线程下已够棘手,一旦并发,错误表现更随机、更难复现。宁可多做一次 Stat 或 EvalSymlinks,也别假设 os.MkdirAll 能兜底所有边界情况。


















