我有两个线程,并且这些线程之间共享了一个 float-type 值。 在 Thread1 上写入浮点型值并由 Thread2 读取。 Thread1 和 Threda2 同时启动。
private const int BUFFER_SIZE = 65536 * 8;
private float _readWriteValue = 0;
private bool _stop = false;
private void Thread1()
{
Random ran = new Random();
while (!_stop)
{
_readWriteValue = ran. Next(1, 100);
}
}
private void Thread2()
{
while (!_stop)
{
float[] BufferData = new float[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; i++)
{
BufferData[i] = _readWriteValue;
}
ProcessMethod(BufferData);
}
}
private void ProcessMethod(float[] data)
{
// Do Something
}
所以,当我检查 BufferData 时,它只填充了一个数字。例如,BufferData 只填充了 22。看起来当 _readWriteValue 进入 Thread2 中的 for 循环时,已被锁定,Thread1 无法在其中写入新值。 我正在尝试找出解决方案。我尝试使用 lock、Monitor 和 ConcurrentQueue,但每次都得到相同的结果。 BufferData 仅由一个数字填充。
我对多线程的理解有误吗? 我该怎么办?
该示例不是线程安全的。每当您读取和写入任何共享值时,您都需要某种形式的同步。编译器基本上可以将您的示例重写为:
private void Thread2()
{
var value = _readWriteValue;
while (!_stop)
{
float[] BufferData = new float[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; i++)
{
BufferData[i] = value ;
}
ProcessMethod(BufferData);
}
}
仅将
volatile
添加到 _readWriteValue
可能有助于线程安全方面,但是何时以及是否 volatile 合适存在争议。 Eric Lippert 在原子性、波动性和不变性是不同的,第三部分中写道
坦率地说,我不鼓励你创建一个不稳定的领域
另一种选择是插入内存屏障,但除非你真的知道你在做什么,否则我建议使用锁。
还有一个重大问题,即循环运行所需的时间可能相差很大。如果您想生成一次读取的单个值,您应该使用队列进行通信。有很多方法可以做到这一点,但一个简单的方法是使用阻塞集合:
private BlockingCollection<float> queue = new BlockingCollection();
private void Thread1()
{
Random ran = new Random();
while (!_stop)
{
queue.Enqueue( ran. Next(1, 100))
}
queue.CompleteAdding();
}
private void Thread2()
{
int i = 0;
float[] BufferData = new float[BUFFER_SIZE];
foreach(var value in queue){
i = i + 1 % 10;
BufferData[i] = value;
if(i == 0){
ProcessMethod(BufferData);
}
}
}
您可以指定队列的最大缓冲区大小,这样如果没有更多空间,线程1将阻塞,如果没有可用值,线程2将阻塞。