Многопоточная блокировка, веселые времена (практика для промежуточного экзамена)

#multithreading #locking #thread-safety #deadlock

#многопоточность #блокировка #безопасность потоков #тупик

Вопрос:

Вот вопрос, о котором спорит моя учебная группа:

(g) Рассмотрим следующий код C #:

 public class Demo {
    private static readonly object a = new object();
    private static readonly object b = new object();

    public static void Main (string[] args) {
        Demo d = new Demo();
        Task t1 = Task.Factory.StartNew(d.g);
        Task t2 = Task.Factory.StartNew(d.h);
        t2.Wait();
        t1.Wait();
    }

    private void g() {
        lock (a) {
            lock (b) {
                Console.Write("G");
            }
        }
    }

    private void h() {
        lock (b) {
            lock (a) {
                Console.Write("H");
            }
        }
    }
}
  

Это многопоточная программа, поэтому разные исполнения могут давать разные результаты. Поставьте галочку рядом с полным выводом, который может выдать программа. (Последний вариант означает отсутствие вывода.)
Вывод ответа

Вывод——-Возможен?

 GH  
HG  
G   
H   
(nothing)
  

Что мы думаем:

GH был бы результатом, если бы t1 заблокировал b до того, как t2 заблокировал b.

(ничего) не было бы результатом, если бы t1 заблокировал a, а затем t2 заблокировал b, потому что это привело бы к взаимоблокировке

G был бы выходом, если бы t1 заблокировал b, а затем, пока t1 все еще удерживал блокировку на b, запустился t2, потому что t2.wait ожидал бы завершения t1.

Не могу придумать, как вы могли бы получить H или HG. Тем не менее, один из нас запустил код 200 000 раз, и он иногда получал HG… Я не понимаю

Я просто не уверен в этих ответах. Что вы все думаете? Любая помощь приветствуется!

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

1. Редактировать: я имел в виду, что иногда мы получаем HG. Никогда не получаю ни одной буквы. (Я также отредактировал свой вопрос, я просто хотел убедиться, что люди увидели редактирование, написав его также в комментарии). Кроме того, спасибо за вашу правку 🙂

2. Вам следует отредактировать свой актуальный вопрос, а не публиковать исправления в комментариях. Кроме того, никогда не делайте двойной пробел в коде.

Ответ №1:

Если вы когда-либо получаете одно письмо, это означает, что одному потоку удалось получить обе блокировки. Что также означает, что он сможет снять обе блокировки, и другой поток завершится успешно — и напечатает другое письмо. При таком рассуждении возможны как GH , так и HG возможные результаты. Кроме того, можно зайти в тупик, когда поток 1 удерживает одну блокировку, а поток 2 — другую.

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

1. Единственное, на чем мы застряли сейчас: как t2.wait() и t1.wait() влияют на ситуацию? Один член нашей группы говорит, что t1 может заблокировать a и b, и прежде чем он выпустил b, поток t2 начал запрашивать блокировку для b, которую он не сможет получить, потому что у t1 в настоящее время есть эта блокировка. Затем, в этот момент, до того, как t1 освободил b, и пока t2 все еще запрашивает блокировку для b, основной поток переходит к t2.wait(). Не приведет ли это к зависанию? с возможностью G распечатки перед замораживанием? (извините, что мы тупоголовые!)

2. Упс! Мы разобрались! Прости!

Ответ №2:

Нет абсолютно никакой гарантии, что g() это будет запущено раньше h() . Насколько я понимаю, StartNew() на самом деле не запускает задачу, а ставит ее в очередь для следующего доступного потока.

Ответ №3:

Я думаю, что единственный способ получить только G или только H — это завершить обе функции, но программа завершается до того, как консоль сбрасывает вторую букву.

Ответ №4:

HG может произойти, потому что нет гарантии, когда новый поток начнет выполняться. После создания Task t2 операционной системы вы можете сначала запустить ее, а затем вернуться и запустить t1 .