为什么 Lock 语句不能按预期工作
Why Lock statement doesn't work as expected
static List<int> sharedCollection = new List<int>();
static readonly Object obj = new Object();
static void Main(string[] args)`enter code here`
{
var writeThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
lock (obj)
{
Write();
}
}
});
var readThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
lock (obj)
{
Read();
}
}
});
writeThread.Start();
readThread.Start();
Console.ReadLine();
}
static void Read()
{
Console.Write("Current collection state: ");
sharedCollection.ForEach((e) => Console.Write($"{e} "));
Console.WriteLine();
}
static void Write()
{
Random generator = new Random();
var addedValue = generator.Next(1, 20);
sharedCollection.Add(addedValue);
Console.WriteLine($"Added value is: {addedValue}");
}
我花了很多时间试图理解为什么我收到这个:
console result
有人可以向我解释这段代码有什么问题吗?
Mutex 工作正常,但我也需要说明 lock 语句...
我希望在每次添加第一个线程后,我都会从第二个线程获得一个集合状态。像这样:
Added value: 1
Collection state: 1
Added value: 15
Collection state: 1 15
Added value: 4
Collection state: 1 15 4
我知道您希望这些线程 运行 在某种程度上是并行的,但它们是顺序执行的。你的期望是正确的。
不过,我不认为它与锁有任何关系。 lock 只会阻止读取和写入同时发生,不会产生这种行为。在没有锁的情况下尝试验证。 (但是,由于 JiT 编译器、CPU 缓存失效和优化等原因,如果有锁,即使没有直接影响,结果仍可能不同)。
我最好的选择是读取线程实在是太慢了,它不会在写入完成所有操作之前 一次 完成。编写 UI 是昂贵的,即使是在像控制台这样微不足道的东西上。甚至特别是那里。我使用 robocopy 做了很多用户配置文件的备份。如果它遇到很多非常小的文件,那么仅仅编写控制台就会成为 实际程序瓶颈 ,永远超过 磁盘访问 。磁盘访问出现瓶颈的事情并不经常发生。
如果您只为每个用户触发的事件编写一次 UI,您将不会注意到成本。但是从任何形式的循环中进行——尤其是在另一个线程中的一个 运行ning——你会开始注意到它。我特别得知 foreach 的循环速度显然是 for 循环的一半。
我什至为此做了一个示例,尽管是在 Windows 表单环境中:
using System;
using System.Windows.Forms;
namespace UIWriteOverhead
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int[] getNumbers(int upperLimit)
{
int[] ReturnValue = new int[upperLimit];
for (int i = 0; i < ReturnValue.Length; i++)
ReturnValue[i] = i;
return ReturnValue;
}
void printWithBuffer(int[] Values)
{
textBox1.Text = "";
string buffer = "";
foreach (int Number in Values)
buffer += Number.ToString() + Environment.NewLine;
textBox1.Text = buffer;
}
void printDirectly(int[] Values){
textBox1.Text = "";
foreach (int Number in Values)
textBox1.Text += Number.ToString() + Environment.NewLine;
}
private void btnPrintBuffer_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(10000);
MessageBox.Show("Printing with buffer");
printWithBuffer(temp);
MessageBox.Show("Printing done");
}
private void btnPrintDirect_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(1000);
MessageBox.Show("Printing directly");
printDirectly(temp);
MessageBox.Show("Printing done");
}
}
}
但即使是这种开销也不太可能产生持久的结果。在某些时候,读取线程应该首先获得锁,从而阻止写入。但是,仍然有太多变数无法确定。你应该尝试一个更简单的例子,写得更一致(并且更少)。将 "A" 和 "B" 写入控制台,而不是像这样复杂的东西怎么样?
static List<int> sharedCollection = new List<int>();
static readonly Object obj = new Object();
static void Main(string[] args)`enter code here`
{
var writeThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
lock (obj)
{
Write();
}
}
});
var readThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
lock (obj)
{
Read();
}
}
});
writeThread.Start();
readThread.Start();
Console.ReadLine();
}
static void Read()
{
Console.Write("Current collection state: ");
sharedCollection.ForEach((e) => Console.Write($"{e} "));
Console.WriteLine();
}
static void Write()
{
Random generator = new Random();
var addedValue = generator.Next(1, 20);
sharedCollection.Add(addedValue);
Console.WriteLine($"Added value is: {addedValue}");
}
我花了很多时间试图理解为什么我收到这个: console result
有人可以向我解释这段代码有什么问题吗?
Mutex 工作正常,但我也需要说明 lock 语句... 我希望在每次添加第一个线程后,我都会从第二个线程获得一个集合状态。像这样:
Added value: 1
Collection state: 1
Added value: 15
Collection state: 1 15
Added value: 4
Collection state: 1 15 4
我知道您希望这些线程 运行 在某种程度上是并行的,但它们是顺序执行的。你的期望是正确的。
不过,我不认为它与锁有任何关系。 lock 只会阻止读取和写入同时发生,不会产生这种行为。在没有锁的情况下尝试验证。 (但是,由于 JiT 编译器、CPU 缓存失效和优化等原因,如果有锁,即使没有直接影响,结果仍可能不同)。
我最好的选择是读取线程实在是太慢了,它不会在写入完成所有操作之前 一次 完成。编写 UI 是昂贵的,即使是在像控制台这样微不足道的东西上。甚至特别是那里。我使用 robocopy 做了很多用户配置文件的备份。如果它遇到很多非常小的文件,那么仅仅编写控制台就会成为 实际程序瓶颈 ,永远超过 磁盘访问 。磁盘访问出现瓶颈的事情并不经常发生。
如果您只为每个用户触发的事件编写一次 UI,您将不会注意到成本。但是从任何形式的循环中进行——尤其是在另一个线程中的一个 运行ning——你会开始注意到它。我特别得知 foreach 的循环速度显然是 for 循环的一半。
我什至为此做了一个示例,尽管是在 Windows 表单环境中:
using System;
using System.Windows.Forms;
namespace UIWriteOverhead
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int[] getNumbers(int upperLimit)
{
int[] ReturnValue = new int[upperLimit];
for (int i = 0; i < ReturnValue.Length; i++)
ReturnValue[i] = i;
return ReturnValue;
}
void printWithBuffer(int[] Values)
{
textBox1.Text = "";
string buffer = "";
foreach (int Number in Values)
buffer += Number.ToString() + Environment.NewLine;
textBox1.Text = buffer;
}
void printDirectly(int[] Values){
textBox1.Text = "";
foreach (int Number in Values)
textBox1.Text += Number.ToString() + Environment.NewLine;
}
private void btnPrintBuffer_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(10000);
MessageBox.Show("Printing with buffer");
printWithBuffer(temp);
MessageBox.Show("Printing done");
}
private void btnPrintDirect_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(1000);
MessageBox.Show("Printing directly");
printDirectly(temp);
MessageBox.Show("Printing done");
}
}
}
但即使是这种开销也不太可能产生持久的结果。在某些时候,读取线程应该首先获得锁,从而阻止写入。但是,仍然有太多变数无法确定。你应该尝试一个更简单的例子,写得更一致(并且更少)。将 "A" 和 "B" 写入控制台,而不是像这样复杂的东西怎么样?