Xcode14 使用信号量造成线程优先级反转问题修复
一、前言
应用Xcode 14.1
进行项目编译时,遇到以下错误提示,导致APP线程暂停。
Thread running at QOS_CLASS_USER_INTERACTIVE waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions
以上问题是由于iOS信号量造成线程优先级反转,在并发队列使用信号量会可能会造成线程优先级反转。
经过查询资料,发现是在XCode14上增加了工具,比如 :
Thread Performance Checker
(XCode14上默认开启的),这个工具会让APP在运行的时候,发现有例如线程优先级反转和非UI工作在主线程上运行等问题的时候,就会在XCode问题导航栏中提示该卡顿风险警告,可以帮助我们在开发初期就能发现并解决隐含的卡顿风险问题;这个不是崩溃,如果不想要,可以在 “Product -> Scheme - > Edit Scheme
的 Diagnostics
中去掉 Thread Performance Checker
勾选”。
XCode14还有其他一些新增加的工具类,可参考 iOS卡顿检测。
二、关于线程优先级反转
优先级反转(Priority Inversion
) 指高优先级任务需要等待低优先级任务执行完成才能继续执行,这种情况下优先级被反转了。
举例:有三个线程分别为:A、B、C。优先级A > B > C,线程A和B处于挂起状态,等待某一事件发生,线程C正在运行,此时任务C开始使用共享资源Source。在使用Source时,线程A等待事件到来,线程A转为就绪态,因为线程A优先级比线程C高,所以线程A会立即执行。当线程A要使用共享资源Source时,由于共享资源Source正在被线程C使用,因此线程A被挂起,线程C开始运行。如果此时中等优先级线程B等待事件到来,则线程B转为就绪态。由于线程B优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。直到线程C释放共享资源Source后,线程A才得以执行。在这种情况下,优先级发生了翻转,线程B先于线程A运行。
三、优先级反转会造成什么后果
低优先级的任务比高优先级的任务先执行,导致任务的错乱,逻辑错乱;
可能造成系统崩溃;
死锁;优先级低的线程迟迟得不到调度,具有高优先级的线程不能执行,死锁;
四、怎么避免线程优先级反转
如果当前线程因等待某线程上正在进行的操作如(block1)而受阻,而系统知道block1的所在的目标线程,系统会通过提高相关线程的优先级来解决优先级反转的问题 (如线程A在尝试获取共享资源而被挂起的期间内,将线程C的优先级提升到同线程A的优先级,等线程C处理结束,降回原优先级,这样能防止C被B抢占)。如果不知道block1所在的目标线程,则无法知道应该提高谁的优先级,也就无法解决反转的问题,如信号量。
五、使用信号量可能会造成线程优先级反转,且无法避免
QoS (Quality of Service
),用来指示某任务或者队列的运行优先级;
-
记录了持有者的api都可以自动避免优先级反转,系统会通过提高相关线程的优先级来解决优先级反转问题,如
dispatch_sync
, 如果系统不知道持有者所在的线程,则无法知道应该提高谁的优先级,也就无法解决反转问题。 -
慎用
dispatch_semaphore
做线程同步。dispatch_semaphore
容易造成优先级反转,因为api没有记录是哪个线程持有了信号量,所以有高优先级的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS
); -
dispatch_semaphore
不能避免优先级反转的原因:在调用dispatch_semaphore_wait()
的时候,系统不知道哪个线程会调用dispatch_semaphore_signal()
方法,系统无法知道owner信息,无法调整优先级。dispatch_group
和semaphore
类似,在调用enter()
方法的时候,无法预知谁会leave()
,所以系统也不知道owner信息。
六、延伸阅读:iOS | Xcode中快速打开终端
在 AndroidStudio
、Goland
等 JetBrains IDEA 一系的代码编辑器中,界面底部有一个 Terminal 选项卡。打开选项卡会创建一个 Terminal
,并自动切换到当前项目的根目录下,然后我们就可以在此快速的执行一些命令操作。如下图:
然而,用于 iOS 开发的 Xcode
中并没有该选项卡,这就很不方便了。接下来讲解如何手动为 Xcode 配置一个 Terminal
的快捷入口。
6.1 .sh绑定
步骤1:新建 xcode-terminal.sh
脚本文件
切换到任意目录,然后新建一个 xcode-terminal.sh
的脚本文件,并编辑其内容。
脚本内容如下:
#!/bin/sh
if [ -n "$XcodeProjectPath" ]; then
open -a Terminal "$XcodeProjectPath"/..
else
open -a Terminal "$XcodeWorkspacePath"/..
fi
另外,.sh
前面的文件名称可以自定义,但是下面步骤2中修改权限时,名称必须一致。
步骤2:修改文件执行权限
打开终端,并在其中执行如下命令:
chmod +x 路径名/.sh文件名
如: chmod +x xcode-terminal.sh
步骤3:脚本命令添加到 Xcode 中
依次打开 : Xcode menu > Behaviors > Edit Behaviors…
,
然后点击下图左下角的 + :
然后输入自定义的 Behavior 名称(对应上图中的 2),并指定一个快捷键(对应上图中的3)。
然后勾选上图右侧的 Run(对应上图中的4), 并双击 Run 右侧的下拉框(对应上图中的 5 ),指定该 Behavior 对应的脚本文件——也就是刚才创建的 xcode-terminal.sh。
至此,配置完成。在 Xcode 编辑器中,按下自定义的快捷键就可以调出终端了。
6.2 执行 pod install
脚本
脚本内容如下:
#!/bin/sh
# 改脚本用于Xcode 执行快捷键执行 pod install
path=""
if [ -n "$XcodeProjectPath" ]; then
path=$XcodeProjectPath
else
path=$XcodeWorkspacePath
fi
# 执行 AppleScript 打开 Terminal 进行 podinstall
osascript <<EOF
tell application "Terminal"
activate
do script with command "cd \"$path\"/..;pod install"
end tell
EOF
总结
任意需求都可以通过脚本实现,然后可以将其关联到 Xcode 的 behavious 中,并为其关联快捷键。
七、延伸阅读:Undefined symbol: _rebind_symbols || symbol(s) not found for architecture arm64
xcode 14
给出如下错误提示信息:
Undefined symbols for architecture arm64:
"_rebind_symbols", referenced from:
___32+[RCTReconnectingWebSocket load]_block_invoke in libReact-RCTWebSocket.a(RCTReconnectingWebSocket.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决方案:
- 清理
Xcode
缓存
rm -rf ~/Library/Developer/Xcode/DerivedData/
2)清理CocoaPods
缓存
切换到项目ios目录下,执行以下命令。
rm -rf "${HOME}/Library/Caches/CocoaPods"
rm -rf "`pwd`/Pods/"
pod update
若执行 pod update
命令报错,则执行以下命令:
cd ..
pod install --project-directory=ios
- 最后将
Build Active Architectures Only
设置为NO
。
八、延伸阅读: 2 duplicate symbols for architecture arm64
编译阶段,错误提示信息如下:
duplicate symbol '_OBJC_CLASS_$_Orientation' in:
/Users/ccms-m-03/Library/Developer/Xcode/DerivedData/mrcs-erictiduzoziyxgpkngocqfejvjq/Build/Intermediates.noindex/mrcs.build/Debug-iphoneos/mrcs.build/Objects-normal/arm64/Orientation.o
/Users/ccms-m-03/Library/Developer/Xcode/DerivedData/mrcs-erictiduzoziyxgpkngocqfejvjq/Build/Products/Debug-iphoneos/react-native-orientation/libreact-native-orientation.a(Orientation.o)
duplicate symbol '_OBJC_METACLASS_$_Orientation' in:
/Users/ccms-m-03/Library/Developer/Xcode/DerivedData/mrcs-erictiduzoziyxgpkngocqfejvjq/Build/Intermediates.noindex/mrcs.build/Debug-iphoneos/mrcs.build/Objects-normal/arm64/Orientation.o
/Users/ccms-m-03/Library/Developer/Xcode/DerivedData/mrcs-erictiduzoziyxgpkngocqfejvjq/Build/Products/Debug-iphoneos/react-native-orientation/libreact-native-orientation.a(Orientation.o)
ld: 2 duplicate symbols for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决方案:根据提示搜索重复文件Orientation。
若存在重复文件,则删掉一个。
此外,也可以通过以下步骤检查:
-
首先排查是否有名字重复的文件。(查看下自己的项目中创立的文件名和引入的第三方文件名是否重复)。
-
检查是否在
#import
头文件的时候,不小心把.h
写成了.m
(可以全局搜索是否是这个问题). -
仔细在报错的类中找下是否有重复添加
.h
头文件。
九、延伸阅读:Xcode编译报错:LLDB is likely reading from device memory to resolve symbols.
ios应用在本地热部署启动过程中,控制台给出以下提示信息:
Launching “**” is taking longer than expected. Do you want to continue to wait?
LLDB is likely reading from device memory to resolve symbols.
问题分析:
大概意思是编译时间会比预期的要长,是否继续等待。主要是新操作系统和Xcode旧版的架构不匹配造成的。
解决方案:
通过访达,“前往文件夹”功能输入~/Library/Developer/Xcode/
,进入iOS DeviceSupport目录,删除该真机对应的架构文件(比如iOS15.1,就删除iOS15.1的架构文件),退出Xcode,拔掉手机,重新连接打开Xcode,解决。
如果上面方案不行,选择Xcode->Window->Devices and Simulators(command+shift+2),鼠标右键点击真机设备,选择Unpair Device。解除信任,然后重新拔插手机,重新信任,重启Xcode。
- 点赞
- 收藏
- 关注作者
评论(0)