简体   繁体   English

为什么 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}");
}

I spend a lot of time trying to understand why I receive this: console result我花了很多时间试图理解为什么我会收到这个:控制台结果

Could someone explain to me what is wrong with this code?有人可以向我解释这段代码有什么问题吗?

Mutex works fine but I need to illustrate lock statement too... I expect that after every adding in 1st thread I obtain a collection state from the 2nd thread.互斥锁工作正常,但我也需要说明锁定语句......我希望在每次添加第一个线程后,我都会从第二个线程获得一个集合 state。 Like this:像这样:

Added value: 1
Collection state: 1
Added value: 15
Collection state: 1 15
Added value: 4
Collection state: 1 15 4

I understand you expeected those threasd to run somewhat in paralell, but instead they executed sequentially.我了解您希望这些线程在某种程度上并行运行,但它们是按顺序执行的。 You expectation is correct.你的期望是正确的。

I do not think it has anything to do with lock, however.但是,我认为这与锁定无关。 lock will only prevent a read and a write from happening at the same time, not produce this behavior. lock 只会阻止读取和写入同时发生,不会产生这种行为。 Try it without the lock to verify.不带锁试试看。 (However due to things like the JiT Compiler, CPU cache invalidations and Optimisations, results may still differet if there is a lock, even if it has no direct effect). (但是,由于 JiT 编译器、CPU 缓存失效和优化等原因,如果有锁,结果可能仍然不同,即使它没有直接影响)。

My best bet is that the read thread is simply so slow, it does not finish once before the write is through all it's itteartions.我最好的选择是读取线程太慢了,在写入完成之前它不会完成一次 Writing the UI is expensive, even on something as trivial as the console.编写 UI 的成本很高,即使是在控制台这样微不足道的东西上也是如此。 Or even especially there.甚至特别是那里。 I do a lot of backups of userprofiles using robocopy.我使用 robocopy 对用户配置文件进行了很多备份。 And if it hits a lot of very small files, just writing the Console becomes the actuall programm bottleneck , ever over disk access .如果它遇到很多非常小的文件,那么仅仅编写控制台就会成为实际的程序瓶颈,超过磁盘访问 And something out-bottlenecking disk acess is not something that happens often.超出磁盘访问瓶颈的事情并不经常发生。

If you write the UI only once per user triggerd event, you will not notice the cost.如果您只为每个用户触发的事件编写一次 UI,您将不会注意到成本。 But do it from any form of loop - especially one running in another thread - and you will start to notice it.但是从任何形式的循环中执行它——尤其是在另一个线程中运行的循环——你会开始注意到它。 I was particualry informed that a foreach is apparently half as slow at ittearting as a for loop.我被特别告知,foreach 的运行速度显然是 for 循环的一半。

I even made a example for this, albeit in a Windows Forms Environment:我什至为此做了一个例子,尽管在 Windows Forms 环境中:

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");
        }
    }
}

But even this overhead is pretty unlikey to have a presistent result.但即使是这种开销也不太可能产生持久的结果。 At some time the read thread should get the lock first, blocking write.在某些时候,读线程应该首先获得锁,阻塞写。 But still, there are too many variables to say for sure.但是,仍然有太多变数无法确定。 You should propably try a simpler example, with more consistent (and a whole lot less) writework.您应该尝试一个更简单的示例,使用更一致(并且少得多)的书写工作。 What about writing "A" and "B" to the console, instead of complex stuff like this?将“A”和“B”写入控制台怎么样,而不是像这样复杂的东西?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM