使用 gcc 原子操作,您可以快速测试配置是否更改(比较两个整数的时间),并且您可以加载新配置...ALL WITHOUT LOCKING。
伪代码:
假设我有一个配置结构 C。假设有一个全局变量 _pConfig 指向当前配置。
struct C *_pConfig;
加载新配置:
// allocate a new struct C
struct C *pNewconfig = malloc(sizeof(struct C));
...
// load the pNewconfig struct from disk or something
...
// let the config struct have a next pointer so we can save list of configs for freeing later
pNewconfig->pNext = _pConfig;
// when done loading pNewconfig. change the global. not before done!, else threads see unf data!
// 32 bit assignment is atomic (usually).
// If not atomic on your platform, use __sync_bool_compare_and_swap()
_pConfig = pNewConfig;
// is safe to free old cfgs with 0 use counts. Make sure old is old enough so that there is no chance
// a thread could have been swapped out anywhere between point A and B below (next code section).
for (struct C *pCfg=pNewconfig->pNext ; pCfg ; pCfg=pCfg->pNext) {
// Free last pcfg (!pCfg->pNext) if its not in use and its old enough.
// Don't have to atomically check cUse here since once it changes to zero, its zero forever.
// nBirthday could be clock ticks when the config was created. nNow could be ticks now.
if (!pCfg->pNext && !pCfg->cUse && pCfg->nBirthDay-nNow > OldEnough) {
free(pCfg);
break;
}
}
现在我们如何使用它...每个线程都需要保留一个ptr来构造线程配置的结构C。如果 _pConfig 更改,每个线程都可以通过将线程的结构 C 地址与当前地址进行比较来判断。假设 pthread 是指向线程数据的指针。
while (1) {
// POINT A
struct C *pConfig = _pConfig; // make a copy in case it changes
// super quick check with no locking!
if (pConfig == pThread->pConfig)
break; // no change...get out quick.
__sync_add_and_fetch(&pConfig->cUse, 1); // increment use count of new cfg
// POINT B
__sync_sub_and_fetch(&pThread->pConfig->cUse, 1); // decriment use count of thread's cfg
pThread->pConfig = pConfig; // use new cfg
// do whatever you do with the cfg data
}
使用 __sync_add、_sub,因为多个线程可以同时访问新配置,这样可以确保准确的使用计数。
请注意在 A 点和 B 点之间调度的线程的停顿时间。由于时间片通常为 1 毫秒,因此长时间的停顿可能是 10-100 毫秒,除非调度程序被占用。如果您的配置数据很小并且每小时只更改几次,我可能会在释放它之前保留它几天。选择一个足够大的时间,您知道没有停滞的线程。如果某个线程由于某种原因长时间没有检查新配置,请不要担心......使用计数将> 1并且不会被释放。