Вопрос потока C #, почему здесь бесконечный цикл?

#c# #multithreading

#c# #многопоточность

Вопрос:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadExample
{
    public class Info
    {
        public int Counter;


        private static object _lock = new object();
        private List<Thread> ThreadList;

        public Info(int counter)
        {
            Counter = counter;

            ThreadList = new List<Thread>();

            ThreadList.Add(new Thread(ThreadBody));
            ThreadList.Add(new Thread(ThreadBody));

           ThreadList[0].Name = "t1";
           ThreadList[1].Name = "t2";

        }

        public void Start()
        {
            ThreadList.ForEach(t => t.Start(t.Name));
        }

        public void ThreadBody(object name)
        {
            while (Counter != 20)
            {
                lock (_lock)
                {
                    Counter  ;

                    Console.WriteLine("Thread {0} : the value of the counter is {1}", name.ToString(), Counter);
                }
            }



        }

    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Info info = new Info(0);
            info.Start();
        }
    }
}
  

если блокировка — это просто счетчик блокировок
блокировка (_lock)
{
Счетчик ;
}
У меня нет бесконечного цикла, но если блокировка такая, как в примере, она запускает бесконечный цикл

Комментарии:

1. Не уверен, что понимаю, о чем именно вы спрашиваете. Есть ли шанс, что вы можете прояснить дальше?

2. Вам нужно предоставить больше контекста. Поведение, которое вы видите, и то, что вы пытались.

Ответ №1:

Может случиться так, что, когда Counter доходит до 19, оба потока входят в цикл, и в конечном итоге он увеличивается до 21, прежде чем они снова проверят значение.

Вам нужно будет удерживать блокировку во время чтения значения Counter . Counter Может быть достаточной двойная проверка (перечитайте ее еще раз внутри while цикла, удерживая блокировку). Однако я не уверен в этом, потому что моя голова просто не может отслеживать все детали различных моделей потоковой памяти между native, .NET, Java и чем угодно. Даже в .NET модель ECMA, по-видимому, отличается от того, что MS гарантирует для своей среды CLR (см. http://msdn.microsoft.com/en-us/magazine/cc163715.aspx и http://www.bluebytesoftware.com/blog/PermaLink ,guid, 543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx). Для получения более подробной информации о том, почему двойная проверка может работать, а может и не работать, найдите «блокировка с двойной проверкой» — за чем-то, что, по-видимому, должно быть простым, скрывается ужасно много сложностей.

Например, вот фрагмент запуска на моей машине:

 Thread t1 : the value of the counter is 1
Thread t2 : the value of the counter is 2
Thread t2 : the value of the counter is 3
Thread t2 : the value of the counter is 4
Thread t2 : the value of the counter is 5
Thread t2 : the value of the counter is 6
Thread t2 : the value of the counter is 7
Thread t2 : the value of the counter is 8
Thread t2 : the value of the counter is 9
Thread t2 : the value of the counter is 10
Thread t2 : the value of the counter is 11
Thread t2 : the value of the counter is 12
Thread t2 : the value of the counter is 13
Thread t2 : the value of the counter is 14
Thread t2 : the value of the counter is 15
Thread t2 : the value of the counter is 16
Thread t2 : the value of the counter is 17
Thread t2 : the value of the counter is 18
Thread t2 : the value of the counter is 19
Thread t2 : the value of the counter is 20
Thread t1 : the value of the counter is 21
Thread t1 : the value of the counter is 22

... Thread t1 never stops ...
  

Вы заметите, что это t2 останавливается, как только оно достигает Counter значения 20, но t1 этого не замечает. Он уже вошел в цикл (или решил войти в цикл), думая, что Counter равно 1 (или, может быть, 2 или что-то еще — только не 20).

Комментарии:

1. Я согласен — это, скорее всего, проблема.

2. Даже если только один поток не достигнет этого значения до 21, это будет продолжаться «вечно».

3. итак, решение состоит в том, чтобы использовать оператор ‘while’ внутри оператора ‘lock’

4. @Robert: Или, еще лучше, используйте <20 вместо !=20

Ответ №2:

Проблема в вашей строке здесь:

 while (Counter != 20)
  

Поскольку вы фиксируете приращение к счетчику, в какой-то момент Counter может равняться 19. Оба потока могут выполнить проверку, затем увеличить счетчик внутренне, сделав его 21, прежде чем потоки проверят снова.

При этом, даже если два потока не достигают этого одновременно, один поток может увидеть 20 и остановиться, в то время как другой поток имеет значение 21, когда это достигается, и цикл будет продолжаться вечно.

Ваше «исправление» (блокировка только приращения) на самом деле не исправляет это, кстати — это просто делает случай ошибки менее вероятным. Причина этого в том, что Console.WriteLine вызов выполняется намного, намного медленнее, поэтому больше времени обработки происходит в вашей блокировке, что повышает вероятность того, что потоки увеличат скорость после вашей проверки состояния, прежде чем они увидят это снова. Однако это все еще может произойти при простой блокировке приращения счетчика (хотя это было бы реже.)

Вы могли бы легко исправить это, установив более гибкое условие, такое как:

  while (Counter < 20)
  

Это приведет к завершению потоков, как только оно достигнет 20 или выше.

Комментарии:

1. Как насчет того, чтобы я просто заблокировал счетчик . блокировка (_lock) { Counter ; } Как насчет использования nterlocked. Увеличение (счетчик ссылок)

2. @retide: Пока вы проверяете Counter != 20 , вы рискуете, что один поток будет работать вечно, поскольку «другой» поток может увеличить его до 21.

Ответ №3:

Способ написания вашего кода позволяет обоим потокам увеличивать размер, Counter прежде чем будет оценено их соответствующее while предложение. В этом случае Counter можно перейти от 19 к 21 до того, как будет нажат следующий while .

Попробуйте рефакторинг вашего цикла во что-то вроде:

 while (true) {
    lock (_lock) {
        Counter  ;
        Console.WriteLine("Thread {0} : the value of the counter is {1}",
            name.ToString(), Counter);
        if (Counter >= 20) {
            break;
        }
    }
}