2015-04-28 3 views
4

Для конкретной потокобезопасной структуры данных мне необходимо защитить доступ к центральной структуре данных (а именно к массиву байтов). Я предпочитаю использовать ReentrantLocks в этом случае, поскольку это политика справедливости, а также расширенные возможности при создании нескольких условий.Несколько условий против множественных замков

Условия параллельности являются сложными и перечислены ниже:

  1. Центральный массив должен быть защищен исключительно (то есть один поток сразу).
  2. Два метода доступа (foo и bar) должны иметь возможность запускаться одновременно (блокируя внутренне, если они попытаются получить доступ к массиву центрального байта).
  3. Призывы к любому методу (foo и bar) должны быть эксклюзивными (то есть множественные вызовы foo из разных потоков приведут к блокировке потока).

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

ReentrantLock lockFoo = new ReentrantLock(true); 
ReentrantLock lockCentral = new ReentrantLock(true); 

Condition centralCondition = lockCentral.newCondition(); 

public void foo(){ 
    // thread-safe processing code here 

    lockFoo.lock();   
    lockCentral.lock(); 

    try{ 
     // accessing code here 

     try{ 
      // waits upon some condition for access 
      while(someCondition){ 
       centralCondition.await(); 
      } 
     }catch(InterruptedException ex){ 
      // handling code here 
     } 

     // more processing 
    }finally{ 
     lockCentral.unlock(); 
     lockFoo.unlock(); 
    } 
} 

структура эквивалентна по методу bar, просто с другим объектом блокировки lockBar. Кроме того, код упростил мое более сложное многоуровневое ожидание и сигналы к одному условию для простоты.

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

Вместо этого я пытался реорганизовать внешний замок (lockFoo и lockBar) в качестве условия lockCentral вместо этого, как показано ниже:

ReentrantLock lockCentral = new ReentrantLock(true); 

Condition fooCondition = lockCentral.newCondition(); 
Condition centralCondition = lockCentral.newCondition(); 

boolean isInFoo = false; 

public void foo(){ 
    // thread-safe processing code here 

    lockCentral.lock(); 

    try{ 
     // implement method exclusiveness via fooCondition 

     try{ 
      while(isInFoo){ 
       fooCondition.await(); 
      } 

      isInFoo = true; 
     }catch(InterruptedException ex){ 
      return; 
     } 

     // accessing code here 

     try{ 
      // waits upon some condition for access 
      while(someCondition){ 
       centralCondition.await(); 
      } 
     }catch(InterruptedException ex){ 
      // handling code here 
     } 

     // more processing 
    }finally{ 
     isInFoo = false; 
     fooCondition.signal(); 

     lockCentral.unlock(); 
    } 
} 

после некоторого осмотра я не могу решить между ли бывший является лучшая идея или последняя (особенно с включением этого случайного булева). Идея упрощения кода, по-видимому, привела к тому, что код длиннее, в этом случае очень контрастно интуитивно понятный.

Есть некоторые конвенции или веские причины, чтобы дать аргумент либо:

  1. Используя один замок за блокировки контекста (прежний код, в котором различные причины блокировки акций различных замков).

  2. Использование одного замка за каждый ресурс блокировки (последний код, где центральная структура, которая должна быть защищена, использует единый замок, а все остальные реализованы как условия для доступа к указанной структуре).

ответ

5

последние коды отличаются от прежней только вручную реализации замка lockFoo с fooCondition (то же самое верно и для бара части) о связанных.

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

Помимо служебных целей, прежний код является предпочтительным, поскольку он самодокументирован. Кроме того, он может быть расширен до того случая, когда необходимо получить доступ к данным, защищенным lockFoo, без lockCentral. В этом случае ручная реализация блокировки теряет коэффициент усиления.

+0

Хм ... Я вижу вашу точку. Я не думал о выигрыше в производительности от последнего, пытается ли блокировать ReentrantLock, что приводит к существенной деградации производительности (под угрозой)? Особенно с накладными расходами на доступ к глобальному 'lockCentral' (поскольку' fooCondition' должен каким-то образом распознать центральный мьютекс). Тесты не дают мне окончательных результатов на моем компьютере, и это действительно очень важный сегмент кода. – initramfs

0

В оригинальном примере lockFoo и lockBar замков полностью избыточные, так как ни foo(), ни bar() может выполнять любую работу без блокировки lockCentral замка. Если вы не измените дизайн программы, то вам понадобится lockCentral.


Вы сказали, что вы думали, что ваш первый пример был «слишком сложно», но ваш второй пример пути более сложнее. Похоже, вы просто пытаетесь заменить lockFoo и lockBar кодом блокировки вашего собственного дизайна. Но в чем смысл? Это не будет делать что-нибудь отличное от того, что делает ваш первый пример.


Какова цель блокировки в любом случае? Вы сказали: «Звонки на любой метод (foo и bar) должны быть эксклюзивными». Это начинается с неправильной ноги: не используйте замки для защиты методов; Используйте блокировки для защиты данные.

Что это за "центральный массив байтов?" Что делают потоки? Почему нужно ли его защищать?

Каковы данные, на которых работает foo()? Зачем его нужно защищать? Каковы данные, по которым работает bar()? Почему что необходимо защитить?

Важнейшим из них является то, что данные foo() и данные в баре() должны быть защищены как , так и в виде центрального массива байтов?

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

SomeObject someObject; 
SomeOtherObject someOtherObject; 
boolean success = false; 
while (! success) { 

    someLock.lock(); 
    try { 
     someObject = getLocalCopyOfSomeData(); 
     someOtherObject = getLocalCopyOfSomeOtherData(); 
    } finally { 
     someLock.unlock(); 
    } 

    doTheRealWork(someObject, someOtherObject); 

    someLock.lock(); 
    try { 
     success = updateCentralCopyOf(someObject) || updateCentralCopyOf(someOtherObject); 
    } finally { 
     someLock.unlock(); 
    } 
} 
+0

Во-первых, я не сказал, что foo и bar должны быть эксклюзивными, я сказал, что им нужно работать ** одновременно **. Возможно, я слишком сильно упростил ситуацию. Я могу заверить вас, что код не может выжить на одном «lockCentral», так как это приведет к повреждению данных.Обратите внимание на то, что 'lockCentral' должен быть выпущен и повторно приобретен во время выполнения метода, когда какое-то условие вызывает' await() '(в моей реальной реализации он должен делать это несколько раз, поэтапно). – initramfs

+0

Возьмем следующий пример: если поток A входит в метод foo, содержит 'lockCentral', модифицирует некоторые переменные, а затем освобождает' lockCentral' как часть условия. Оппортунистическая нить B могла бы затем удерживать 'lockCentral', вводить метод foo и изменять переменные недопустимым образом. 'lockFoo' есть/был там, чтобы остановить любой другой поток, вводя метод foo, пока нить A не завершится (т. е. будет передаваться другим потоком из другого метода [например, bar]). – initramfs

+0

@CPUTerminator Мои комментарии основывались в основном на вашем первом примере. Код в первом примере не позволит одному потоку выполнять _work_ в foo(), а другой поток работает в bar(), потому что ни один из этих методов не работает, не удерживая центральный замок. Я признаю, что ранее не заметил, что для одного потока можно ожидать 'wait()' в foo(), а другой поток работает в bar(). Но, я все еще сомневаюсь в том, что нужно разрешить одному потоку в foo() и one into bar(), если они не могут одновременно работать _work_? –

Смежные вопросы