让我们想象一下,我正在编写一个具有相当大量的只读全局数据的库,需要在使用该库之前对其进行初始化。例如,全局数据可能是应用程序逻辑的各个部分的查找表,这些部分在程序的生命周期内不会发生变化。
现在我有几种方法来初始化这些数据:
init()
函数。现在,如果我的数据是只读的,并且对于库在中运行的每个环境应该是相同的,那么(3)是相当吸引人的。即使在这种情况下它也有一些缺点:如果数据非常大(但很容易在程序上生成)膨胀的大小很多(例如,具有50K代码但8MB查找表的库最终将达到8050K左右)。类似地,源本身可能非常大,或者构建系统需要在编译时处理源的生成。
您可能无法使用(3)的主要原因是表可能是固定的(只读),但需要在运行时生成,因为它们嵌入了一些有关环境的信息(例如,环境变量的值) ,我配置设置从文件读取,有关机器架构的信息,等等)。这些数据无法嵌入到源中,因为它取决于运行时环境。
所以我们至少有方法(1)和(2) - 但我不知道如何以简单的方式使这些线程安全。通过不改变任何全局状态,库的其余部分可以是线程安全的 - 就像绝大多数C函数可以用线程安全的方式编写而不用任何明确使用线程原语。
但是我无法为这个全局init找出类似的替代方案:
(1)是不受欢迎的,因为我们不想要求用户调用此方法,并且在任何情况下它只是将问题移动到调用代码:然后调用代码需要组织调用此{{1}使用该库,在任何线程使用该库之前,在所有线程中只使用一次方法。
(2)失败,因为对库的并发调用可能会执行双初始化。
在C ++中,您可以使用方法调用初始化全局变量,例如init()
。 C中有任何等价物吗?或者我是否坚持使用线程原语(因平台而异,例如,int data[] = loadData()
,pthread_once
以及Windows所拥有的)只是为了获得我的线程安全初始化?
答案 0 :(得分:0)
我不知道任何与平台无关的方式以线程安全的方式初始化库。这并不奇怪,因为C中没有独立于平台的线程模型。
所以你的解决方案将是特定于平台的。
@ThingyWotsit在评论中提到使用C ++来初始化你的库,这将是线程安全的。但它可能会将您锁定到特定的C ++运行时,因此它可能不是您的C共享对象/库的有用解决方案。您可能不愿意或无法添加对C ++的依赖,您可能特别不愿意或无法锁定特定的C ++运行时。
对于GCC,you can use the __attribute((constructor))
在加载共享对象时调用你的iniitaliziation函数:
constructor destructor constructor (priority) destructor (priority)
构造函数属性导致在执行main()之前自动调用该函数。 类似地,析构函数属性会导致调用该函数 在main()完成或exit()被调用后自动执行。 具有这些属性的函数对于初始化数据非常有用 将在程序执行期间隐式使用。
您可以提供可选的整数优先级来控制订单 运行哪个构造函数和析构函数。一个构造函数 较小的优先级编号在具有较大的构造函数之前运行 优先号码;析构函数的相反关系成立。所以, 如果你有一个分配资源和析构函数的构造函数 解除分配相同资源,两个函数通常都有 优先权相同。构造函数和析构函数的优先级 与为命名空间范围的C ++对象指定的相同(请参阅 C ++属性)。
例如:
static __attribute__((constructor)) void my_lib_init_func( void )
{
...
}
您的代码将在调用main()
之前运行。
如果您的库是动态加载的(显式调用dlopen()
,例如),则在加载库时将调用init函数,并且在返回之前不会将库视为已加载。
其他编译器提供功能相同的#pragma init()
:
#pragma init(my_lib_init_func)
static void my_lib_init_func( void )
{
...
}
请参阅#pragma init and #pragma fini using gcc compiler on linux
对于Windows? Windows C ++运行时非常稳定且无处不在。我只是在Windows上使用C ++解决方案,特别是如果您正在使用MSVC进行编译。 (但请参阅评论......)
答案 1 :(得分:0)
在可能的情况下,选项3始终是首选。你对缺点的推理是错误的。如果可执行文件中有8MB常量表,则它可以在任何远程现代操作系统上由程序的所有实例或共享库的用户直接映射和共享。如果在运行时生成它,则每个进程都有自己的表副本。
当选项3不可用时,您必须使用pthread_once
或同等版本,或使用锁定实现您自己的版本(效率低得多)。几乎没有理由使用奇怪的操作系统特定的替代品;所有主要平台本身都支持POSIX线程API,或者拥有在平台的低级原语之上提供它的现有库。