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