我有两个线程,并且这些线程之间共享了一个
float
类型的值。浮点型值写入 Thread1
并通过 Thread2.
读取
Thread1
和 Thread2
同时开始。
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
进入for
中的Thread2
循环时,已被锁定并且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(boundedCapacity:100);
private void Thread1()
{
Random ran = new Random();
while (!_stop)
{
queue.Add( 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 将阻塞。