Я могу избегать использования блокировок для своей seldomly-изменяющейся переменной?

Я читал книгу Joe Duffy по Параллельному программированию. У меня есть вид академического вопроса о незапертой поточной обработке.

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

Тем не менее, у меня есть вопрос: предположите, что у меня есть класс с международным свойством на нем.

Значение, на которое ссылается это свойство, будет читаться очень часто несколькими потоками

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

Если это действительно изменяется, в то время как другая операция, которая использует его, находится в полете, никто не собирается потерять палец (первая вещь, которую любой использующий его делает скопировать его в локальную переменную),

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

Однако даже энергозависимый может наложить хит производительности.

Что, если я использую VolatileWrite, когда он изменяется, и оставляют доступ нормальным для чтений. Что-то вроде этого:

public class MyClass
{
  private int _TheProperty;
  internal int TheProperty
  {
    get { return _TheProperty; }
    set { System.Threading.Thread.VolatileWrite(ref _TheProperty, value); }
  }
}

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

11
задан Paŭlo Ebermann 15 August 2011 в 16:32
поделиться

7 ответов

Маркировка переменной как «волатильный» имеет два эффекта.

1) Читает и пишеты имеют приобретение и выпустить семантику, так что читает и пишет другие места памяти не будут «двигаться вперед и назад во времени» в отношении чтения и записи этой памяти. (Это упрощение, но вы берете мою точку зрения.)

2) Код, сгенерированный джиттером, не будет «кэш» значение, которое, кажется, логически не изменяется.

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

Но мне кажется, что последняя точка вполне актуальна. Если у вас есть спиновой замок на ненулительной переменной:

while(this.prop == 0) {}

Джиттер находится в его правах, чтобы генерировать этот код, как если бы вы написали

if (this.prop == 0) { while (true) {} }

, действительно ли это так или нет, я не знаю, но Это имеет право на. Если то, что вы хотите, для кода на самом деле повторно проверить свойство на каждом переходе на петлю, маркируя его как волатильный - это правильный путь.

6
ответ дан 3 December 2019 в 07:37
поделиться

Вопрос в том, увидит ли поток чтения когда-либо изменение. Вопрос не только в том, увидит ли он сразу .

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

4
ответ дан 3 December 2019 в 07:37
поделиться

Этот шаблон я использую, когда к ситуации применяется шаблон "последний пишущий". Я использовал ключевое слово volatile, но после того, как увидел этот паттерн в примере кода от Джеффри Рихтера, я начал его использовать.

1
ответ дан 3 December 2019 в 07:37
поделиться

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

   <LinearLayout android:layout_width="fill_parent"
                android:layout_height="fill_parent">
        <ImageView android:layout_height="fill_parent"
                 android:layout_width="0dp"
                 android:layout_weight="1"/>
        <ImageView android:layout_height="fill_parent"
                 android:layout_width="0dp"
                 android:layout_weight="1"/>
   </LinearLayout>

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

-121--738044-

Это ошибка WinINet, поэтому связанное с ней сообщение находится на WinINet.dll. Вы просто должны сообщить об этом FormatMessage (), чтобы получить правильное сообщение:

FormatMessage( 
   // flags:
   FORMAT_MESSAGE_ALLOCATE_BUFFER  // allocate buffer (free with LocalFree())
   | FORMAT_MESSAGE_IGNORE_INSERTS // don't process inserts
   | FORMAT_MESSAGE_FROM_HMODULE,  // retrieve message from specified DLL
   // module to retrieve message text from
   GetModuleHandle(_T("wininet.dll")),
   // error code to look up
   errCode,
   // default language
   0, 
   // address of location to hold pointer to allocated buffer
   (LPTSTR)&lpMsgBuf, 
   // no minimum size
   0, 
   // no arguments
   NULL );

Это официально задокументировано на MSDN в разделе "Обработка ошибок" документации WinINet.

Обратите внимание, что можно снова добавить флаг FORMAT _ MESSAGE _ FROM _ SYSTEM , если вы хотите использовать эту подпрограмму для ошибок, которые могут или могут не исходить из WinINet: с установленным флагом FormatMessage () будет возвращаться в таблицу системных сообщений, если ошибка не найдена в wininet.dll. Однако не снимают флаг FORMAT_MESSAGE_IGNORE_INSERTS .

-121--2394891-

Да, каждый процессор в конечном итоге увидит изменение адреса памяти. Даже без замков или барьеров памяти. Блокировки и барьеры просто гарантируют, что все это произошло в относительном заказе (w.r.t другие инструкции), так что это оказалось правильным для вашей программы.

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

Поэтому чтение может выглядеть старым или из устаревшего кэша, но на самом деле это произошло раньше, чем ожидалось (обычно из-за предварительного просмотра и предсказания ветвления). Когда он действительно был прочитан, кэш был когерентным, он только что изменился с тех пор. Значит, значение не было старым, когда вы его читали, но теперь оно вам нужно. Вы просто читаете его слишком рано.: - (

Или эквивалентно, он был написан позже, чем логика вашего кода думал, что он будет написан.

Или оба.

В любом случае, если бы это было C/C + +, даже без блокировок/барьеров, вы бы в конечном итоге получили обновленные значения. (обычно в течение нескольких сотен циклов, так как память занимает примерно столько времени).В C/C + + можно использовать volatile (слабый энергонезависимый поток), чтобы убедиться, что значение не было прочитано из регистра. (Теперь есть некогерентный кэш! т.е. регистры)

В C # я не знаю достаточно о CLR, чтобы знать, как долго значение может оставаться в регистре, или как гарантировать, что вы получите реальное перечитывание из памяти. Вы потеряли «слабую» волатильность.

Я бы заподозрил, что пока переменный доступ не будет полностью скомпилирован, у вас в конечном итоге закончатся регистры (x86 не так много, чтобы начать с) и вы перечитаете.

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

2
ответ дан 3 December 2019 в 07:37
поделиться

«Хит производительности» из волатильный , потому что компилятор теперь генерирует код, чтобы на самом деле проверять значение вместо того, чтобы оптимизировать это - другими словами, вы имеют Эта производительность ударила независимо от того, что вы делаете.

2
ответ дан 3 December 2019 в 07:37
поделиться

Я опубликовал свой предыдущий ответ, прежде чем увидел ваш комментарий о том, что вывод является открытым текстом. Теперь попробуйте:

$(document).ready(function() {  
    $("#formID").submit(function () {
        $(":text").each(function () {
            var value = $(this).val();
            var myname  = $(this).attr('name');
            var newValue = value + " \n";
            var hid   = '<input type="hidden" name="' + myname + '" value="' + newValue + '"/>';
            $(this).removeAttr('name');
            $("#formID").append(hid);
        });
    });
});

Предыдущий ответ

Как говорит Марк, символ разрыва строки не визуализируется как разрыв строки при просмотре в браузере (или клиент электронной почты), поэтому вместо добавления символа разрыва строки «\n »следует добавить < br/>

$("#formID").submit(function () {
    $(":text").each(function () {
        var value = $(this).val();
        var newValue = value + '<br/>';
        $(this).val(newValue);
    });
})
-121--4950662-

Этот стиль обеспечивает максимальную точность, доступную в ЛЮБОЙ архитектуре, при назначении значения PI.

-121--1018855-

В C # тип int безопасен для потоков.

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

Однако вы можете объявить его изменчивым , если обновление будет выполняться потоком ОС.

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

a = !a;

, не является атомарным. Если одновременно считываются два потока, возникает условие гонки.

0
ответ дан 3 December 2019 в 07:37
поделиться

Для нормальных вещей (например, для устройств с отображением памяти), протоколы кэш-валюта, происходящие внутри/между процессорами CPU/CPU, существуют для того, чтобы разные потоки, разделяющие эту память, получили согласованное представление о вещах (т.е., если я изменю значение ячейки памяти в одном процессоре, то будет видно другим процессорам, у которых память в кэше). В связи с этим волатильность позволит гарантировать, что оптимизатор не оптимизирует удаленные обращения к памяти (которые все равно всегда проходят через кэш), скажем, чтением значения, кэшированного в регистре. Документация на C# на этот счет выглядит вполне понятной. Опять же, прикладной программист, как правило, сам не имеет дела с кэш-валютой.

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

1
ответ дан 3 December 2019 в 07:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: