第一章:MacOS Ventura/Sonoma系统下VSCode配置Go开发环境的现状与挑战
在 macOS Ventura(13.x)及最新 Sonoma(14.x)系统上,VSCode 配置 Go 开发环境面临多重兼容性与权限模型的叠加挑战。Apple 自 Ventura 起强化了完全磁盘访问(Full Disk Access)、辅助功能(Accessibility)和开发者工具签名验证机制,导致部分 Go 工具链(如 gopls、go-outline、dlv)在首次启动时静默失败,且错误日志常被系统沙盒过滤,难以定位。
Go 运行时与系统架构适配问题
Sonoma 默认仅支持 Apple Silicon(ARM64)原生二进制,但许多用户仍通过 Rosetta 2 运行 Intel 版 VSCode 或旧版 Go SDK。需确认 Go 安装架构一致性:
# 检查当前终端架构与 Go 架构是否匹配
arch # 输出 arm64 或 x86_64
go version # 应显示 go1.21+ darwin/arm64 或 darwin/amd64
file $(which go) # 确认二进制为 Mach-O universal 或单一架构
若架构不一致(如 arm64 终端调用 amd64 Go),gopls 可能崩溃并触发 VSCode 的“Language Server crashed”提示。
VSCode 扩展权限与签名验证
Go 扩展(v0.39+)依赖 gopls 作为核心语言服务器,而 Sonoma 对未公证(notarized)的 CLI 工具执行更严格检查。常见现象包括:
gopls启动后立即退出,进程列表中不可见;- VSCode 输出面板显示
Failed to start gopls: fork/exec ... Operation not permitted。
解决路径:
- 将
gopls二进制拖入「访达」→ 右键「显示简介」→ 勾选「锁定」→ 关闭窗口(绕过首次运行警告); - 在「系统设置 → 隐私与安全性 → 完全磁盘访问」中添加 VSCode 和
/usr/local/go/bin; - 手动重签名(适用于自编译
gopls):codesign --force --deep --sign - /usr/local/go/bin/gopls
Go 工具链初始化差异
Ventura/Sonoma 的 PATH 继承机制变化导致 VSCode 内置终端与 GUI 启动的环境变量不一致。推荐使用以下方式确保工具链可见:
- 在 VSCode 设置中启用
"go.gopath": "/Users/yourname/go"; - 在
settings.json中显式指定工具路径:"go.toolsManagement.autoUpdate": true, "go.goplsPath": "/Users/yourname/go/bin/gopls" - 避免使用 Homebrew 安装的
go(可能混用/opt/homebrew/bin/go与/usr/local/go),统一通过官方.pkg安装并软链至/usr/local/go。
| 问题类型 | 典型表现 | 推荐验证命令 |
|---|---|---|
| 权限拒绝 | Operation not permitted 错误 |
tccutil reset All com.microsoft.VSCode |
| 工具缺失 | gopls not found 提示 |
go install golang.org/x/tools/gopls@latest |
| 环境变量丢失 | go mod 命令在 VSCode 终端中不可用 |
echo $PATH \| grep -E "(go|bin)" |
第二章:Go语言在VSCode中依赖的三大核心env变量深度解析
2.1 GOPATH:被弃用却仍影响模块解析路径的隐式锚点
Go 1.11 引入模块(module)后,GOPATH 不再是构建必需项,但其环境变量仍参与路径解析逻辑——尤其在 go mod download 或 replace 指令未显式覆盖时。
隐式 fallback 行为
当 go build 遇到未在 go.mod 中声明的本地依赖(如 import "mylib"),且无 replace 规则时,Go 工具链会:
- 先尝试
$GOPATH/src/mylib - 再回退至模块缓存(
$GOMODCACHE) - 最终失败于“import path not found”
典型冲突场景
| 场景 | GOPATH 设置 | 行为结果 |
|---|---|---|
| 未设置 GOPATH | 空值或默认 ~/go |
仍触发 $HOME/go/src/... 查找 |
| GOPATH=/tmp/gopath | /tmp/gopath/src/mylib 存在 |
误加载非模块化旧代码 |
GOPATH 多路径(:/a:/b) |
仅首路径生效 | 后续路径被忽略 |
# 查看当前隐式影响
go env GOPATH GOMODCACHE
# 输出示例:
# GOPATH="/home/user/go"
# GOMODCACHE="/home/user/go/pkg/mod"
此输出揭示:即使项目启用
go mod init,GOPATH仍决定go get的 fallback 源目录与pkg/mod缓存根位置,构成模块解析的“影子锚点”。
graph TD
A[go build] --> B{import path resolved?}
B -- No --> C[Check GOPATH/src]
C --> D{Path exists?}
D -- Yes --> E[Load legacy GOPATH package]
D -- No --> F[Fail or use sumdb]
2.2 GOROOT:VSCode Go插件自动探测失效时的手动校准实践
当 VSCode 的 Go 扩展无法自动识别 GOROOT(如多版本 Go 并存、非标准安装路径或 go env 输出异常),需手动校准。
常见失效场景
go version可执行,但Go: Locate Tools报GOROOT not foundGOPATH正确而GOROOT显示为空或/usr/local/go(与实际which go路径不符)
校准步骤
- 终端执行
go env GOROOT获取真实路径 - 在 VSCode 设置中搜索
go.goroot,填入绝对路径(如/home/user/sdk/go1.22.3) - 重启 VSCode 或执行
Go: Restart Language Server
验证配置有效性
# 检查 Go 工具链是否被正确加载
$ ls $(go env GOROOT)/bin/go
# 输出应为:/home/user/sdk/go1.22.3/bin/go
该命令验证
GOROOT指向的bin/go是否存在。若报错No such file,说明路径错误或权限不足;$(go env GOROOT)是 shell 展开语法,确保使用当前 shell 环境下的真实值。
| 参数 | 说明 |
|---|---|
go env GOROOT |
获取 Go 安装根目录(非 GOPATH) |
go.goroot |
VSCode Go 插件专用配置项 |
graph TD
A[VSCode 启动] --> B{Go 插件读取 go.goroot}
B -- 未设置 --> C[尝试自动探测]
B -- 已设置 --> D[直接使用指定路径]
C -- 失败 --> E[语言服务器初始化失败]
D --> F[正常加载 go toolchain]
2.3 GOBIN:解决“command not found”与多版本Go工具链冲突的关键枢纽
GOBIN 是 Go 构建系统中控制二进制输出路径的核心环境变量,直接影响 go install 的可执行文件落点与 Shell 的 $PATH 可发现性。
为什么 command not found 常源于 GOBIN 配置缺失?
默认情况下,go install 将二进制写入 $GOPATH/bin(若 GOBIN 未设置),但该目录往往不在系统 $PATH 中。
正确配置示例
# 推荐:显式设置 GOBIN 并加入 PATH(~/.zshrc 或 ~/.bashrc)
export GOBIN="$HOME/go/bin"
export PATH="$GOBIN:$PATH"
✅ 逻辑分析:
GOBIN覆盖默认安装路径;$GOBIN:$PATH确保新安装的gopls、stringer等命令优先被 Shell 解析。参数"$HOME/go/bin"避免空格与权限问题,且与模块化 Go 工作流解耦。
多版本 Go 共存时的隔离机制
| 场景 | GOBIN 设置 | 效果 |
|---|---|---|
| 全局统一 | /usr/local/go/bin |
冲突风险高(不同 Go 版本生成不兼容二进制) |
| 版本感知 | $HOME/go1.21/bin / $HOME/go1.22/bin |
通过切换 GOROOT + GOBIN 实现工具链硬隔离 |
graph TD
A[go install github.com/golangci/golangci-lint/cmd/golangci-lint] --> B{GOBIN set?}
B -->|Yes| C[写入 $GOBIN/golangci-lint]
B -->|No| D[写入 $GOPATH/bin/golangci-lint]
C --> E[Shell 从 $PATH 查找并执行]
D --> F[若 $GOPATH/bin 不在 $PATH → command not found]
2.4 CGO_ENABLED:macOS M1/M2芯片下Cgo构建失败的根源定位与绕行策略
根源:ARM64与Clang工具链的ABI不匹配
M1/M2芯片运行原生ARM64 macOS,但部分Homebrew安装的gcc或旧版Xcode Command Line Tools仍默认启用-m64(x86_64)目标,导致Cgo链接阶段符号解析失败。
快速验证方式
# 检查当前CGO环境与架构一致性
go env CGO_ENABLED GOARCH CC
# 输出示例:
# CGO_ENABLED="1"
# GOARCH="arm64"
# CC="clang" # ✅ 正确;若为 "gcc" 或含 "-arch x86_64" 则风险高
该命令确认Go构建链是否在ARM64上下文中启用Cgo。CC=clang是Apple Silicon推荐编译器;若CC指向非Apple Clang(如MacPorts gcc),则头文件路径与系统库ABI不兼容,引发undefined symbol: _clock_gettime等错误。
推荐绕行策略
- 临时禁用Cgo(纯Go依赖场景):
CGO_ENABLED=0 go build -o app . - 强制统一架构(需C扩展时):
CC=clang CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o app .
| 策略 | 适用场景 | 风险 |
|---|---|---|
CGO_ENABLED=0 |
无C依赖、纯Go项目 | 无法使用net, os/user等需系统调用的包 |
CC=clang + GOARCH=arm64 |
含SQLite、zlib等C扩展 | 需确保所有C头文件为ARM64切片 |
graph TD
A[go build] --> B{CGO_ENABLED=1?}
B -->|Yes| C[调用CC编译C代码]
C --> D{CC是否为Apple Clang?}
D -->|No| E[ABI不匹配 → 构建失败]
D -->|Yes| F[成功链接arm64系统库]
B -->|No| G[跳过C部分 → 纯Go构建]
2.5 GODEBUG:启用gopls调试日志与VSCode语言服务器卡顿问题的精准诊断
当 gopls 在 VSCode 中响应迟缓,首要线索藏于其底层运行时行为中。启用调试日志需通过环境变量精准控制:
GODEBUG=gopls=1 code --log trace
GODEBUG=gopls=1激活 gopls 内置的细粒度 tracing(非通用 Go 运行时调试),仅影响语言服务器自身状态流转,不干扰类型检查或语义分析流程。
日志分级与关键字段
gopls.trace:记录 LSP 请求/响应生命周期gopls.cache:暴露包加载与依赖图构建耗时gopls.modfile:定位go.mod解析瓶颈
常见卡点对照表
| 现象 | 对应日志关键词 | 典型诱因 |
|---|---|---|
| 打开文件后无响应 | cache.Load hang |
vendor/ 下巨型依赖 |
| 跳转定义超时 | findReferences slow |
GOPATH 混合多模块 |
| 自动补全延迟 | completion.cache miss |
go.work 未正确激活 |
诊断流程图
graph TD
A[启动 VSCode] --> B{设置 GODEBUG=gopls=1}
B --> C[观察 output > gopls 面板]
C --> D[定位 last logged action]
D --> E[结合 go env 和 workspace layout 分析]
第三章:VSCode Go扩展与Shell环境变量的协同机制
3.1 VSCode启动方式差异(GUI vs Terminal)导致env变量丢失的实证分析
环境变量加载路径差异
GUI 启动(如 Dock/Spotlight/桌面图标)绕过 shell 初始化流程,不读取 ~/.zshrc 或 ~/.bash_profile;终端启动则继承当前 shell 的完整环境。
实证对比实验
# 在终端中执行
echo $PATH | grep -o '/usr/local/bin' # 输出存在
code --status | grep "env:" # 显示完整 PATH
此命令验证终端启动时 VSCode 继承了 shell 的
$PATH。关键在于code命令由 shell 解析并 fork,子进程自然继承environ。
# GUI 启动后,在 VSCode 集成终端中运行
printenv | grep -E '^(PATH|HOME|PYTHONPATH)$'
此处
PATH缺失/usr/local/bin等用户路径——证明 GUI 进程由launchd直接派生,未 source 用户配置文件。
启动机制对比表
| 启动方式 | 父进程 | 加载 shell 配置 | 继承自定义 env |
|---|---|---|---|
| GUI(图标) | launchd | ❌ | ❌ |
| Terminal | zsh/bash | ✅ | ✅ |
修复路径示意
graph TD
A[GUI 启动] --> B[launchd 加载 Info.plist]
B --> C[无 shell profile 加载]
D[Terminal 启动] --> E[zsh -i -c 'code']
E --> F[读取 ~/.zshrc → export PATH]
3.2 launch.json与settings.json中env字段对Go调试器的实际作用边界
env 字段的生效层级差异
launch.json 中的 env 仅注入到 调试进程及其子进程(如 dlv 启动的 go run);而 settings.json 的 go.toolsEnvVars 仅影响 VS Code Go 扩展启动的工具进程(如 gopls、go list),不传递给调试器或用户代码。
调试环境变量传递链
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "test",
"env": {
"GODEBUG": "mmap=1",
"MY_VAR": "from-launch"
}
}
]
}
此配置使
dlv进程继承env,再由dlv在exec时透传给被调试的 Go 程序。GODEBUG将实际生效于运行时内存分配行为;但os.Setenv()在程序内修改的变量不会反向同步回调试器。
作用边界对比表
| 配置位置 | 影响进程 | 是否透传至被调试 Go 程序 | 修改后是否热生效 |
|---|---|---|---|
launch.json.env |
dlv → go test/run |
✅ 是 | ❌ 否(需重启调试) |
settings.json.go.toolsEnvVars |
gopls, go mod download |
❌ 否 | ✅ 是(部分工具) |
graph TD
A[VS Code] --> B[launch.json.env]
A --> C[settings.json.go.toolsEnvVars]
B --> D[dlv process]
D --> E[Go program under debug]
C --> F[gopls / go list]
F -.x.-> E
3.3 使用shellCommand extension实现终端级env注入的工程化方案
传统 .env 文件仅作用于当前 shell 会话,难以在 VS Code 集成终端中自动生效。shellCommand 扩展提供了一种声明式注入机制,支持跨平台环境变量预载入。
核心配置示例
// .vscode/settings.json
{
"shellCommand.commands": [
{
"command": "export NODE_ENV=staging && export API_BASE=https://api.staging.example.com",
"name": "load-staging-env",
"runInTerminal": true,
"autoRun": true
}
]
}
该配置在终端启动时自动执行 export 命令链;autoRun: true 触发时机为终端创建瞬间;runInTerminal: true 确保变量注入到实际 shell 进程而非扩展沙箱。
支持的环境变量注入方式对比
| 方式 | 作用域 | 持久性 | 是否需重启终端 |
|---|---|---|---|
shellCommand 自动执行 |
当前终端会话 | 会话级 | 否 |
terminal.integrated.env.* |
所有新终端 | 进程级 | 是(需重开终端) |
.bashrc/.zshrc |
用户全局 | 永久 | 否(但需 source) |
执行流程示意
graph TD
A[VS Code 启动] --> B{检测 terminal 打开事件}
B --> C[触发 autoRun 命令]
C --> D[在终端进程内执行 export]
D --> E[环境变量立即生效于后续命令]
第四章:macOS系统级环境治理与VSCode Go工作流整合
4.1 Ventura/Sonoma中zsh与LaunchAgents环境变量同步的配置范式
核心矛盾:Shell 与 LaunchAgent 的环境隔离
macOS Ventura/Sonoma 默认使用 zsh,但 LaunchAgents(.plist)进程由 launchd 启动,不继承 shell 的 ~/.zshrc 环境变量,导致 GUI 应用或后台服务无法识别自定义路径(如 JAVA_HOME、PATH 扩展)。
推荐范式:统一注入点 + 惰性加载
在 ~/Library/LaunchAgents/ 中部署 env-sync.plist,通过 ProgramArguments 调用 shell 初始化脚本:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.env.sync</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>source "$HOME/.zshenv" 2>/dev/null; exec "$HOME/bin/launch-env-helper"</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
逻辑分析:
launchd不解析~/.zshrc,但支持sh -c执行命令;~/.zshenv是 zsh 启动时最早加载的全局初始化文件(无交互限制),比.zshrc更适合作为环境源。exec确保子进程继承全部导出变量,避免 fork 开销。
关键配置对照表
| 文件位置 | 加载时机 | 是否被 launchd 继承 | 推荐用途 |
|---|---|---|---|
~/.zshenv |
zsh 启动即加载 | ✅(需显式 source) | 全局环境变量(PATH/JAVA_HOME) |
~/.zshrc |
仅交互式 shell 加载 | ❌ | 别名、函数、提示符 |
~/Library/LaunchAgents/*.plist |
用户登录时启动 | ❌(空环境) | 必须显式注入环境 |
同步机制流程图
graph TD
A[User Login] --> B[launchd loads local.env.sync.plist]
B --> C[sh -c 'source ~/.zshenv']
C --> D[export all variables]
D --> E[exec launch-env-helper with full env]
4.2 Homebrew安装Go后GOROOT动态更新与VSCode自动识别失效修复
Homebrew 安装 Go 后,GOROOT 指向 /opt/homebrew/opt/go/libexec(Apple Silicon)或 /usr/local/opt/go/libexec(Intel),但 VSCode 的 Go 扩展常缓存旧路径或依赖 go env GOROOT 输出,导致调试/语法检查失败。
常见失效原因
- VSCode 启动时未重新读取 shell 环境变量
go命令由 Homebrew 管理,但GOROOT未显式导出到用户 shell 配置- Go 扩展启用
gopls时跳过go env自动探测,依赖启动时环境快照
修复步骤
-
在
~/.zshrc(或~/.bash_profile)中添加:# 显式导出 Homebrew Go 的 GOROOT(适配 Apple Silicon) export GOROOT="$(brew --prefix go)/libexec" export PATH="$GOROOT/bin:$PATH"逻辑分析:
brew --prefix go动态获取当前 Homebrew Go 安装根路径,避免硬编码;$GOROOT/bin确保go、gofmt等工具优先被识别。导出后需执行source ~/.zshrc并重启 VSCode。 -
在 VSCode 设置中强制指定 Go 工具路径: 设置项 值 go.goroot/opt/homebrew/opt/go/libexec(M1/M2)或/usr/local/opt/go/libexec(Intel) -
重启 VSCode 并运行
Developer: Reload Window,验证Go: Locate Configured Go Tools是否全部绿色通过。
4.3 使用direnv管理项目级Go env变量并联动VSCode工作区设置
direnv 是轻量级环境隔离工具,可为每个 Go 项目自动加载专属 GOPATH、GOBIN 和 GOWORK 等变量,避免全局污染。
安装与启用
# macOS 示例(Linux 类似)
brew install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
该行将 direnv 钩子注入 shell 启动流程,使每次 cd 进入含 .envrc 的目录时自动执行权限校验与环境加载。
项目级 .envrc 示例
# .envrc
use_golang 1.22
export GOPATH="$(pwd)/.gopath"
export GOBIN="$GOPATH/bin"
export GOWORK="$(pwd)/go.work"
use_golang 是 direnv 内置插件(需 direnv allow 后生效),自动设置 GOROOT 并校验版本;GOPATH 和 GOBIN 局部化保障构建可复现性。
VSCode 工作区联动
| 设置项 | 值 | 说明 |
|---|---|---|
go.gopath |
${workspaceFolder}/.gopath |
与 .envrc 中 GOPATH 严格一致 |
go.toolsGopath |
${workspaceFolder}/.gopath |
确保 gopls、goimports 等工具路径统一 |
graph TD
A[cd into project] --> B{direnv loads .envrc}
B --> C[export GOPATH GOBIN GOWORK]
C --> D[VSCode reads workspace settings]
D --> E[gopls uses same GOPATH → 语义补全精准]
4.4 macOS隐私权限(完整磁盘访问)对gopls文件监控能力的影响验证
权限缺失时的行为表现
当 gopls 未获「完整磁盘访问」权限时,fsnotify 无法监听 /Users/*/Library/ 或挂载卷下的 Go 工作区变更:
# 查看当前 gopls 进程的沙盒限制
sudo log show --predicate 'subsystem == "com.apple.sandbox"' \
--info --last 1h | grep -i "gopls.*denied"
此命令捕获系统沙盒日志中
gopls的拒绝事件。subsystem == "com.apple.sandbox"精确过滤沙盒策略日志;--last 1h限定时间窗口避免冗余;grep提取与gopls相关的denied记录,直接反映权限拦截点。
验证路径监控能力对比
| 路径类型 | 有完整磁盘访问 | 无该权限 |
|---|---|---|
~/go/src/ |
✅ 实时触发 | ❌ 静默丢弃 |
/Volumes/External/ |
✅ | ❌ |
/tmp/ |
✅(无需权限) | ✅ |
权限授予流程
- 打开「系统设置 → 隐私与安全性 → 完整磁盘访问」
- 点击「+」添加
/usr/local/bin/gopls(或 VS Code 中嵌入的 gopls 二进制路径) - 重启编辑器使
fsnotify.FSEvents重连内核事件源
graph TD
A[gopls 启动] --> B{检查 sandbox 权限}
B -->|允许| C[注册 FSEvents 监控]
B -->|拒绝| D[降级为 polling 模式]
C --> E[毫秒级文件变更响应]
D --> F[默认 1s 轮询,CPU 升高]
第五章:面向未来的Go开发环境可持续演进路径
Go语言生态正以年均18%的速度增长开发者基数(2023 Stack Overflow Developer Survey),但大量团队仍困于“静态工具链”——go mod版本锁定、CI/CD中硬编码的Go版本、IDE插件滞后于Go 1.22新语法支持。可持续演进不是追逐每个新版本,而是构建可验证、可回滚、可协作的环境生命周期管理体系。
自动化版本治理策略
采用 gvm + go-version GitHub Action 实现双轨控制:本地开发强制使用 .go-version 文件声明最小兼容版本(如 1.21.6),CI流水线则通过 actions/setup-go@v4 动态拉取该版本并缓存。某电商中台项目实践显示,此方案将因Go版本不一致导致的测试失败率从12.7%降至0.3%。关键配置示例如下:
# .github/workflows/ci.yml 片段
- uses: actions/setup-go@v4
with:
go-version-file: '.go-version'
cache: true
智能依赖健康度看板
基于 go list -json -m all 输出构建依赖图谱,接入Grafana监控三类指标:
- 过期模块占比(对比Go.dev最新发布版本)
- 高危CVE影响模块数(对接OSV API)
- 未维护模块(GitHub stars 18个月)
| 模块名 | 当前版本 | 最新版本 | CVE数量 | 维护状态 |
|---|---|---|---|---|
| github.com/gorilla/mux | v1.8.0 | v1.9.1 | 2 | 活跃 |
| gopkg.in/yaml.v2 | v2.4.0 | v2.4.0 | 1 | 归档 |
可重现的IDE配置分发
放弃手动配置VS Code Go插件,改用.vscode/settings.json + devcontainer.json组合:
settings.json声明"go.gopath"和"go.toolsManagement.autoUpdate"为truedevcontainer.json预装gopls@v0.14.2及delve@1.21.1,并挂载团队统一的gopls配置文件
某金融科技团队通过此方式将新成员环境就绪时间从4.2小时压缩至11分钟,且杜绝了因gopls版本碎片化导致的代码补全失效问题。
跨云CI/CD一致性保障
在AWS CodeBuild、GitHub Actions、GitLab CI中统一采用容器镜像ghcr.io/company/go-env:1.21-bullseye,该镜像由Terraform管理构建流程:
resource "aws_ecr_repository" "go_env" {
name = "go-env"
}
# 构建触发器绑定到go.mod变更
使用Mermaid绘制环境演进决策流:
flowchart TD
A[检测go.mod变更] --> B{是否引入新major版本?}
B -->|是| C[启动兼容性测试套件]
B -->|否| D[直接部署预编译镜像]
C --> E[生成diff报告]
E --> F[人工评审]
F --> G[合并或拒绝]
团队知识资产沉淀机制
每个Go版本升级后,自动归档三类产物至内部Wiki:
go tool compile -gcflags="-S"生成的汇编差异快照pprof火焰图对比基准(HTTP吞吐量/内存分配)go vet新增检查项的实际误报率统计表
某SaaS平台在升级至Go 1.22时,通过分析-gcflags="-S"输出发现range循环优化使核心服务GC停顿降低23ms,该数据直接驱动了全栈性能调优优先级重排。
