Зачем вам блокировать темы? - PullRequest
7 голосов
/ 18 июня 2011

Я читал много примеров блокировки потоков ... но зачем вам их блокировать? Насколько я понимаю, когда вы инициируете потоки без присоединения к ним, они будут конкурировать с основным потоком и всеми другими потоками за ресурсы, а затем будут выполняться, иногда одновременно, иногда нет.

Гарантирует ли блокировка, что потоки НЕ выполняются одновременно?

Кроме того, что не так с потоками, выполняющими одновременное выполнение? Разве это не лучше? (более быстрое общее исполнение)

Когда вы заблокируете темы, они заблокируют их все или вы можете выбрать, какие из них вы хотите заблокировать? (Что бы ни делала блокировка ...)

Я имею в виду использование функций блокировки, таких как lock () и получения в модуле потоков, кстати ...

Ответы [ 2 ]

14 голосов
/ 18 июня 2011

Блокировка позволяет вам заставить несколько потоков обращаться к ресурсу по одному, а не пытаться одновременно обращаться к ресурсу всем из них.

Как вы заметили, обычно требуется, чтобы потоки выполнялись одновременно,Однако представьте, что у вас есть два потока, и они оба записывают в один файл.Если они попытаются выполнить запись в один и тот же файл в одно и то же время, их выходные данные будут смешанными, и ни одному из потоков не удастся фактически вставить в файл то, что он хотел.

Теперь, возможно, эта проблема не будетприходить все время.В большинстве случаев потоки не будут пытаться записать в файл все сразу.Но иногда, может быть, один раз в тысячу пробежек, они делают.Так что, возможно, у вас есть ошибка, которая кажется случайной, и которую трудно воспроизвести, и, следовательно, ее трудно исправить.Тьфу!

Или, может быть ... и это произошло в компании, в которой я работал ... у вас есть такие ошибки, но вы не знаете, что они есть, потому что они крайне редки, если на вашем компьютере есть толькомало процессоров, и вряд ли у кого-либо из ваших клиентов их больше 4. Затем они все начинают покупать блоки по 16 процессоров ... и ваше программное обеспечение запускает столько потоков, сколько имеется процессорных ядер, так что внезапно вы получаете много сбоев или получаетеневерные результаты.

Так или иначе, вернемся к файлу.Чтобы потоки не наступали друг на друга, каждый поток должен получить блокировку файла перед записью в него.Только один поток может удерживать блокировку одновременно, поэтому только один поток может записывать в файл одновременно.Поток удерживает блокировку до завершения записи в файл, а затем снимает блокировку, чтобы другой поток мог использовать файл.

Если потоки записывают в разные файлы, эта проблема никогда не возникает.Так что это одно решение: пусть ваши потоки пишут в разные файлы, а затем объединяют их при необходимости.Но это не всегда возможно;иногда есть только одно из чего-то.

Это не обязательно файлы.Предположим, вы пытаетесь просто посчитать количество вхождений буквы «А» в кучу разных файлов, по одному потоку на файл.Вы думаете, ну, очевидно, я просто сделаю так, чтобы все потоки увеличивали одну и ту же область памяти каждый раз, когда они видят «А»Но!Когда вы увеличиваете переменную, в которой хранится счетчик, компьютер считывает переменную в регистр, увеличивает регистр и затем сохраняет значение обратно.Что, если два потока читают значение одновременно, увеличивают его в одно и то же время и сохраняют обратно одновременно?Они оба начинают, скажем, с 10, увеличивают его до 11, сохраняют 11 обратно.Таким образом, счетчику 11, когда он должен быть 12: вы потеряли один счет.

Получение блокировок может быть дорогостоящим, так как вы должны ждать, пока кто-либо еще, использующий ресурс, не будет с ним закончен.Вот почему глобальная блокировка интерпретатора Python является узким местом в производительности.Таким образом, вы можете решить вообще не использовать общие ресурсы.Вместо того, чтобы использовать одну ячейку памяти для хранения числа «A» в ваших файлах, каждый поток ведет свой подсчет, и вы добавляете их все в конце (как это ни странно, похоже на решение, которое я предложил для файлов).

9 голосов
/ 18 июня 2011

Во-первых, замки предназначены для защиты ресурсов; потоки не «заблокированы» или «разблокированы»: они / получают / блокируют (для ресурса) и / освобождают / блокируют (для ресурса).

Вы правы в том, что хотите, чтобы потоки выполнялись одновременно как можно дольше, но давайте посмотрим на это:

y=10

def doStuff( x ):
    global y
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y

t1 = threading.Thread( target=doStuff, args=(8,) )
t2 = threading.Thread( target=doStuff, args=(8,) )
t1.start()
t2.start()
t1.join()
t2.join()

Теперь вы можете знать, что любой из этих потоков может завершиться и напечатать сначала. Вы ожидаете увидеть оба выхода 30.

Но они не могут.

y является общим ресурсом, и в этом случае биты, которые читают и записывают в y, являются частью того, что называется «критической секцией», и должны быть защищены блокировкой. Причина в том, что вы не получаете единицы работы: любой поток может получить ЦП в любое время.

Думайте об этом так:

t1 успешно выполняет код, и он набирает

a = 2 * y

Теперь t1 имеет = 20 и на некоторое время останавливается. t2 становится активным, в то время как t1 ожидает больше процессорного времени. t2 выполняет:

a = 2 * y
b = y / 5
y = a + b + x

в этот момент глобальная переменная y = 30

t2 останавливается на короткое время, и t1 снова поднимается. выполняет:

b = y / 5
y = a + b + x

Поскольку у было 30, когда было установлено b, b = 6, а у теперь установлено 34.

порядок отпечатков также недетерминирован, и вы можете получить 30 первых или 34 первых.

используя замок, мы получили бы:

global l
l = threading.Lock()
def doStuff( x ):
    global y
    global l
    l.acquire()
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y
    l.release()

Это обязательно делает этот раздел кода линейным - только один поток за раз. Но если вся ваша программа последовательная, вам все равно не следует использовать потоки. Идея состоит в том, что вы получаете ускорение на основе процента кода, который у вас есть, который может выполнять внешние блокировки и работать параллельно. Это (одна из причин), почему использование потоков в двухъядерной системе не удваивает производительность для всего.

сама блокировка также является общим ресурсом, но это необходимо: как только один поток получает блокировку, все другие потоки, пытающиеся получить / same / lock, будут блокироваться до тех пор, пока она не будет снята. После освобождения первый поток, который продвинется вперед и получит блокировку, заблокирует все остальные ожидающие потоки.

Надеюсь, этого достаточно, чтобы продолжить!

...