最近在接入到一些游戏项目中的时候发现存在比较多关于 libc.so 的 crash,在游戏中某个场景在部分手机会在短时间就直接 Crash,且集中在性能比较好的手机中。经过一番折腾,最后被定位在了一个地方:pthread_key_t
Crash 表现
当时从 Crash上报平台捕获到的 crash 调用栈各种分析最终测试都是正常。
在最开始的排查时候采用了各种常用方法最终以失败告终,方向错误了,于是再次回到 Crash 栈中来,在崩溃栈中都含有:pthread_once、emutls_get_address、cxa_get_globals、emutls_init相关的关键词,由于平时完全没有接触过这几个函数,对他们的了解比较少。
以及对几个函数调用的源码进行查看,发现这几个函数最终涉及到的都是 pthread 使用或者创建相关的
其中在 cs.android的 emutls.c 源码里有:
static void emutls_init(void) {
if (pthread_key_create(&emutls_pthread_key, emutls_key_destructor) != 0)
abort();
}
这里基本上可以和崩溃栈对应上了,正是这里执行的 abort(),那么原因是否是由 pthread_key_create()引起的呢?继续对 pthread_key_create 研究。
最终发现了
以下的代码会耗尽目前程序中的 key,只创建 pthread_key,而不释放掉
void available_key() {
for (int i = 0; i < PTHREAD_KEYS_MAX; i++) {
pthread_key_t key;
int result = pthread_key_create(&key, detachDestructor);
if (result == JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, "--julis", "create thread key Success");
} else {
__android_log_print(ANDROID_LOG_ERROR, "--julis", "create thread key failed");
}
}
}
Android 系统的 pthread_key_t 关键字的作用以及 pthread_key_t 的使用数量的限制的原因
在 Android 系统(以及其他 POSIX 兼容的系统)中,pthread_key_t
是用于实现线程特定数据(Thread-Specific Data, TSD)的关键字。它允许每个线程存储和访问与该线程相关的数据,而不影响其他线程。
pthread_key_t
的作用
-
线程特定数据存储:
pthread_key_t
允许每个线程存储独立的数据副本。每个线程可以通过pthread_getspecific()
和pthread_setspecific()
函数来设置和获取与该关键字相关联的数据。这对于需要在多个线程中共享状态但又希望每个线程有自己独立副本的场景非常有用。
-
避免数据竞争:
- 使用
pthread_key_t
可以避免多个线程对共享数据的竞争条件。每个线程都有自己的数据副本,因此不需要使用锁来保护数据的访问。
- 使用
-
资源管理:
pthread_key_t
还可以用于管理线程生命周期中的资源。例如,可以在一个线程结束时自动释放与该线程相关的数据,使用pthread_key_create()
时可以指定一个析构函数(destructor),在线程退出时调用该函数来清理资源。
pthread_key_t
的使用数量的限制的原因
-
资源管理:
- 每个
pthread_key_t
关键字都需要在系统中分配一定的资源(如内存和管理结构)。如果没有限制,过多的关键字会导致资源耗尽,影响系统的稳定性和性能。
- 每个
-
性能考虑:
- 每个线程在访问线程特定数据时,系统需要进行一些查找和管理操作。过多的关键字会增加这些操作的复杂性,从而影响性能。
-
实现细节:
- 在许多实现中,
pthread_key_t
的数量限制是由系统的设计决定的。例如,Linux 的 pthread 实现通常限制为 1024 个关键字。这是因为在实现中使用了固定大小的数组或其他数据结构来管理这些关键字。
- 在许多实现中,
-
内存限制:
- 每个关键字的创建和使用都需要占用内存。系统可能会对可用内存进行限制,以确保其他重要的系统功能能够正常运行。
具体限制
在 POSIX 标准中,并没有明确规定 pthread_key_t
的数量限制,但大多数实现(包括 Android 和 Linux)通常会有一个默认的限制。例如,Linux 的 pthread 实现通常限制为 1024 个关键字。这个限制可以通过系统配置或编译选项进行调整,但通常不建议随意更改。
结论
pthread_key_t
是一个强大的工具,用于在多线程环境中管理线程特定的数据。它的数量限制是出于资源管理、性能考虑和实现细节等多方面的原因。在设计多线程应用时,开发者应合理使用线程特定数据,避免过度依赖于 pthread_key_t
,以确保应用的稳定性和性能。
Android 系统的 pthread_key_t 的使用数量的超出限制会导致什么后果
在 Android 系统(以及其他 POSIX 兼容的系统)中,pthread_key_t
用于管理线程特定数据(Thread-Specific Data, TSD)。如果超出了 pthread_key_t
的使用数量限制,可能会导致以下后果:
1. 创建失败
- 返回错误:当尝试创建新的
pthread_key_t
时,如果已经达到系统的限制,pthread_key_create()
函数将返回错误。具体来说,它会返回ENOMEM
(内存不足)或EAGAIN
(资源暂时不可用)等错误代码。这意味着无法创建新的关键字,应用程序将无法使用线程特定数据。
2. 程序崩溃或不稳定
- 未定义行为:如果程序在达到限制后继续尝试使用
pthread_key_t
,可能会导致未定义行为。这可能表现为程序崩溃、数据损坏或其他不可预测的错误。
3. 资源泄漏
- 未释放的资源:如果在达到限制后创建关键字失败,可能会导致某些资源(如内存)未被正确释放,尤其是在没有适当的错误处理机制的情况下。这可能导致内存泄漏,影响系统的性能和稳定性。
4. 性能下降
- 性能问题:即使没有直接导致崩溃或错误,过多的
pthread_key_t
使用可能会导致性能下降。系统在管理和查找这些关键字时的开销会增加,影响多线程应用的整体性能。
5. 影响其他线程
- 影响其他线程的行为:如果一个线程由于超出限制而无法创建新的
pthread_key_t
,这可能会影响到该线程的功能,进而影响到整个应用的行为,尤其是在多线程协作的场景中。
结论
为了避免超出 pthread_key_t
的使用数量限制,开发者应合理设计多线程应用,尽量减少对线程特定数据的依赖,并在创建关键字时进行适当的错误处理。确保在不再需要时及时释放关键字(使用 pthread_key_delete()
),以便为其他线程腾出资源。
应用程序什么会导致Android 系统的 pthread_key_t 的使用数量的超出限制
在 Android 系统中,pthread_key_t
的使用数量限制是由系统实现决定的,通常是固定的(例如,Linux 的实现通常限制为 1024 个关键字)。超出这个限制可能会导致应用程序无法创建新的线程特定数据关键字。以下是一些可能导致 pthread_key_t
使用数量超出限制的原因:
1. 不当的关键字管理
- 未释放关键字:如果在不再需要某个
pthread_key_t
时没有调用pthread_key_delete()
,则该关键字将继续占用资源。随着时间的推移,未释放的关键字可能会累积,最终导致达到系统限制。
2. 频繁创建和销毁关键字
- 动态创建:在应用程序中频繁创建和销毁
pthread_key_t
关键字而没有适当的管理,可能会导致达到限制。例如,在每个线程启动时都创建新的关键字,而没有在线程结束时删除它们。
3. 多线程环境中的错误使用
- 错误的设计模式:在多线程应用中,如果每个线程都创建自己的
pthread_key_t
,而没有共享或重用关键字,可能会迅速达到限制。这种情况在设计不当的线程池或工作线程模型中尤为常见。
4. 缺乏错误处理
- 忽视错误返回:在创建
pthread_key_t
时,如果没有适当的错误处理,程序可能会在达到限制后继续尝试创建新的关键字,导致未定义行为或崩溃。
5. 复杂的线程生命周期管理
- 线程的生命周期管理不当:如果线程的生命周期管理不当,可能会导致某些线程在结束时未能正确清理其使用的关键字,导致资源未释放。
6. 使用库或框架的限制
- 第三方库的使用:某些第三方库可能会在内部使用
pthread_key_t
,如果这些库没有适当管理关键字的创建和销毁,可能会导致应用程序达到系统限制。
7. 长时间运行的应用程序
- 长期运行的服务:在长期运行的服务或守护进程中,随着时间的推移,未释放的关键字可能会逐渐累积,最终导致达到限制。
结论
为了避免超出 pthread_key_t
的使用数量限制,开发者应采取以下措施:
- 合理管理关键字:确保在不再需要时调用
pthread_key_delete()
释放关键字。 - 避免频繁创建:尽量重用关键字,避免在每个线程中动态创建新的关键字。
- 实施错误处理:在创建关键字时,始终检查返回值并处理错误。
- 设计良好的线程模型:在设计多线程应用时,考虑如何有效地管理线程特定数据,避免不必要的资源消耗。