简体   繁体   中英

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. 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. 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).

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. Or even especially there. I do a lot of backups of userprofiles using 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. 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.

I even made a example for this, albeit in a Windows Forms Environment:

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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