Почему оператор Lock не работает должным образом - PullRequest
0 голосов
/ 18 октября 2019
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}");
}

Я провожу много времени, пытаясь понять, почему я получаю это: Результат консоли

Может кто-нибудь объяснить мне, что не так с этим кодом?

Mutex работает нормально, но мне тоже нужно проиллюстрировать оператор блокировки ... Я ожидаю, что после каждого добавления в 1-й поток я получаю состояние сбора из 2-го потока. Как это:

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

1 Ответ

0 голосов
/ 18 октября 2019

Я понимаю, что вы ожидали, что эти три будут работать в параллельном режиме, но вместо этого они выполняются последовательно. Вы ожидаете, что это правильно.

Я не думаю, что это как-то связано с блокировкой. Блокировка только предотвратит одновременное чтение и запись, но не приведет к такому поведению. Попробуйте без блокировки, чтобы проверить. (Однако из-за таких вещей, как JiT-компилятор, аннулирование кэша ЦП и оптимизация, результаты могут все еще отличаться, если есть блокировка, даже если она не оказывает прямого влияния).

Лучше всего, чтобы поток чтения былпросто так медленно, он не заканчивает один раз , пока запись не пройдет через все его указания. Написание пользовательского интерфейса стоит дорого, даже на чем-то тривиальном, как консоль. Или даже особенно там. Я делаю много резервных копий пользовательских профилей, используя robocopy. И если он попадает во множество очень маленьких файлов, простое написание Консоли становится узким местом актуальной программы , когда-либо превышающим доступ к диску . И что-то из-за узкого доступа к диску - это не то, что случается часто.

Если вы пишете пользовательский интерфейс только один раз для события, инициируемого пользователем, вы не заметите стоимость. Но сделайте это из любой формы цикла - особенно из цикла в другом потоке - и вы начнете замечать это. В частности, мне сообщили, что foreach, по-видимому, вдвое медленнее при запуске, чем цикл for.

Я даже сделал пример для этого, хотя и в среде 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");
        }
    }
}

Нодаже такие накладные расходы довольно неприятно иметь постоянный результат. Через некоторое время поток чтения должен сначала получить блокировку, блокирующую запись. Но все же, есть слишком много переменных, чтобы сказать наверняка. Вероятно, вы должны попробовать более простой пример, с более последовательной (и намного меньшей) перепиской. А как насчет записи «A» и «B» на консоль вместо сложных вещей вроде этого?

...