我是 C 和多线程的初学者,对在线程不安全函数上使用锁定和复制有疑问。
我的教科书上写着'伪随机数生成器是此类线程不安全函数的一个简单示例 '
unsigned int next = 1;
int rand(void)
{
next = next*1103515245 + 12345;
return (unsigned int)(next/65536) % 32768;
}
void srand(unsigned int seed)
{
next = seed;
}
我的教科书也写着'锁定和复制方法不适用于
rand()
依赖于调用之间的静态 '看不懂,为什么不能重写
rand()
作为:int rand(void)
{
P(&mutex);
next = next*1103515245 + 12345;
V(&mutex);
return (unsigned int)(next/65536) % 32768;
}
在哪里
void P(sem_t *s); /* Wrapper function for sem_wait */
void V(sem_t *s); /* Wrapper function for sem_post */
这样其他线程就不会影响当前线程的下一个静态变量,从而使其成为线程安全功能?
最佳答案
我认为问题是 next
value 不是线程特定的,因此返回到单个线程的值现在取决于对 rand()
的调用顺序在其他线程中。每次调用都是安全的,但线程仍然相互干扰。如果您想稍后重播模拟,PRNG(伪随机数生成器)的确定性可能很重要。因此,您会发现像 POSIX drand48()
这样的函数集set 提供线程安全和线程不安全的变体:
double drand48(void);
double erand48(unsigned short xsubi[3]);
long jrand48(unsigned short xsubi[3]);
void lcong48(unsigned short param[7]);
long lrand48(void);
long mrand48(void);
long nrand48(unsigned short xsubi[3]);
unsigned short *seed48(unsigned short seed16v[3]);
void srand48(long seedval);
其中,
drand48()
, lrand48()
, mrand48()
不是线程安全的,因为它们使用通过 srand48()
设置的通用保存种子值或 seed48()
.功能
erand48()
, jrand48()
和 nrand48()
是线程安全的,因为种子值(参数列表中的三个 16 位值)被传递给函数。另一个函数,
lcong48()
,有没有搅动一切。它改变了种子以及用于生成随机数的系数,跨线程工作。要恢复到您的代码,您需要制作
rand()
的可重入变体.事实上,POSIX 定义(但标记为过时)可重入 rand()
— rand_r()
.int rand_r(unsigned *seed);
您在每次使用时将指向种子的指针传递给函数。因此,您可以编写自己的变体 —
rand_ts()
(对于“线程安全”):int rand_ts(unsigned *next)
{
*next = *next * 1103515245 + 12345;
return (unsigned int)(*next / 65536) % 32768;
}
现在您可以传递一个保存种子值的变量的地址,它将在每次调用时更新。只要您传递一个具有足够持续时间的线程局部变量(通常是在线程中执行的函数的堆栈上的变量),每个线程就能够计算独立的随机数序列,每个线程的序列都是确定的线。
请注意,这种设计消除了对互斥体的需求。
替代方案
random()
生成器具有更复杂的接口(interface)和更好的随机性属性,但是为不同的线程维护单独的随机值序列是繁琐的(并非不可能,但繁琐且相对较慢)。实际上,该页面建议使用 erand48()
, jrand48()
和 nrand48()
如果您需要跨多个线程的独立序列(因为接口(interface)更简单)。如果仔细阅读,POSIX 页面上主要描述之后的基本原理和应用程序使用信息可能会非常有用。
关于c - 在线程不安全函数上使用锁定和复制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54193160/