这里发生了很多事情:海湾合作委员会和克朗之间的差异,gnu ld和gold之间的差异,以及
--as-needed
链接器标志,两种不同的故障模式,甚至可能有些时间问题。
让我们从如何使用posix线程链接程序开始。
编译器的
-pthread
旗帜是你所需要的。它是一个编译器标志,所以在编译使用线程的代码和链接最终可执行文件时都应该使用它。当你使用
-线程
在链接步骤中,编译器将提供
-lpthread
在链接行的正确位置自动标记。
通常,只在链接最终可执行文件时使用,而在链接共享库时不使用。如果您只是想使库线程安全,但不想强制使用库链接pthreads的每个程序,那么您需要使用运行时检查来查看pthreads库是否已加载,并且仅在pthread库已加载时才调用pthread api。在Linux上,这通常是通过检查“canary”来完成的——例如,对任意符号进行弱引用,如
__pthread_key_create
,只有在加载库时才会定义,如果程序链接时没有该库,则其值为0。
但在你的情况下,你的图书馆
libodr.so
很大程度上取决于线程,因此将它与
-线程
旗帜。
这就引出了第一种失败模式:如果在两个链接步骤中都使用g++和gold,程序将抛出
std::system_error
并表示需要启用多线程。这是因为
——根据需要
旗帜。海湾合作委员会通行证
——根据需要
默认为链接器,而clang(显然)没有。用
——根据需要
,链接器将只记录解析强引用的库依赖项。由于对pthread api的所有引用都很弱,因此它们都不足以告诉链接器libpthread.so应该添加到依赖项列表中(通过
DT_NEEDED
动态表中的条目)。更改为Clang或添加
-Wl,--no-as-needed
flag解决了这个问题,程序将加载pthread库。
但是,等等,为什么在使用gnu链接器时不需要这样做呢?它使用相同的规则:只有强引用才会将库记录为依赖项。不同之处在于gnu ld还考虑来自其他共享库的引用,而gold只考虑来自常规对象文件的引用。结果发现pthread库提供了几个libc符号的重写定义,并且有来自
libstdc++.so
一些符号(例如,
write
)那些有力的参考资料足以让GNU LD记录下来
libpthread.so
作为一种依赖。与设计相比,这更像是一个意外;我不认为将gold更改为考虑来自其他共享库的引用实际上是一个健壮的修复。我认为正确的解决方案是让gcc
--no-as-needed
在
-LP-线程
使用时标记
-线程
。
这就引出了一个问题:为什么在使用posix线程和gold链接器时,这个问题不会一直出现。但是这是一个小的测试程序;一个大的程序几乎可以肯定包含了对那些libc符号的强引用
libpthread.so文件
重写。
现在让我们看看第二种失败模式,其中
Notify()
和
Get()
如果链接到
李博德
使用g++、gold和
-LP-线程
.
在
通知()
,在调用
cv.notify_one()
. 您只需要按住锁来设置ready标志;如果我们更改它以便在此之前释放锁,则线程调用
获取()
将在300毫秒后超时,并且不会阻塞。所以这真的是
notify_one()
那是阻塞,程序死锁是因为
获取()
在等同一把锁。
为什么只有在
_ pthread_key_创建
是
FUNC
而不是
NOTYPE
?我认为符号的类型是一个红鲱鱼,而真正的问题是由于gold没有记录符号版本以供未添加为所需库的库解决的引用。实施
wait_for
电话
pthread_cond_timedwait
,两个版本都有
libpthread
和
libc
. 加载程序可能将引用绑定到了错误的版本,导致无法解锁互斥锁而导致死锁。我给gold做了一个临时补丁来记录这些版本,这使得程序能够工作。不幸的是,这不是一个解决方案,因为该修补程序可能导致ld.so在其他情况下崩溃。
我试着改变
cv.wait_for(...)
到
cv.wait(lock, []{ return ready; })
,并且程序在所有场景中都能完美运行,这进一步表明问题出在
线程状态等待
.
底线是
--不需要
flag将修复这个非常小的测试用例的问题。任何更大的都可能在没有额外标志的情况下工作,因为在
LIPP线程
. (例如,添加对
std::this_thread::sleep_for
在任何地方
在里面
odr.cpp
添加对的强引用
nanosleep
,提出
LIPP线程
在需要的列表中。)
更新:
我已经验证了失败的程序链接到错误的
线程状态等待
. 对于glibc 2.3.2来说,
pthread_cond_t
类型已更改,使用该类型的api的旧版本已更改,以动态分配新的(更大的)结构并将指向该结构的指针存储在原始类型中。所以现在,如果消费线程到达
cv.wait_for
在生产线到达之前
cv.notify_one
,执行
瓦伊塔
调用旧版本的
线程状态等待
,它初始化它认为是旧的
线程状态
在里面
cv
用指针指向新的
线程状态
. 之后,当另一个线程到达
简历通知一
,其实现假定
履历
包含新样式
线程状态
而不是指向一个的指针,所以它调用
pthread_mutex_lock
用指针指向新的
线程状态
而不是指向互斥锁的指针。它锁定了可能的互斥锁,但它永远不会被解锁,因为另一个线程解锁了真正的互斥锁。