2011-01-13 3 views
6

Если у меня есть некоторый код, который выглядит примерно так:Эти указатели в многопоточной среде

typedef struct { 
    bool some_flag; 

    pthread_cond_t c; 
    pthread_mutex_t m; 
} foo_t; 

// I assume the mutex has already been locked, and will be unlocked 
// some time after this function returns. For clarity. Definitely not 
// out of laziness ;) 
void check_flag(foo_t* f) { 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
} 

Есть ли в стандарте C предотвращения оптимизатора от перезаписи check_flag как:

void check_flag(foo_t* f) { 
    bool cache = f->flag; 
    while(cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 

В другом слова, имеет ли сгенерированный код следовать указателю f каждый раз через цикл или компилятор может вытащить разыменование?

Если это is бесплатно вытащить его, есть ли способ предотвратить это? Нужно ли мне когда-нибудь посыпать волатильное ключевое слово? Он не может быть параметром check_flag, потому что я планирую иметь другие переменные в этой структуре, что я не возражаю против оптимизации компилятора таким образом.

Я мог бы прибегнуть к:

void check_flag(foo_t* f) { 
    volatile bool* cache = &f->some_flag; 
    while(*cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 
+0

+1 для размышлений об этом виде проблемы до написания кода с резьбой по пробным ошибкам! –

ответ

3

Обычно, вы должны попытаться заблокировать PTHREAD мьютекс перед ожиданием на объекте условия, как освобождение pthread_cond_wait вызова мьютекс (и выкупать его перед возвращением). Итак, ваша функция check_flag должна быть переписана так, чтобы соответствовать семантике в условии pthread.

void check_flag(foo_t* f) { 
    pthread_mutex_lock(&f->m); 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
    pthread_mutex_unlock(&f->m); 
} 

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

В основном, компилятор знает о семантике pthread_cond_wait, pthread_mutex_lock и pthread_mutex_unlock. Он знает, что он не может оптимизировать чтение памяти в этой ситуации (звонок в pthread_cond_wait в этом примере). Здесь нет понятия барьера памяти, просто специального знания определенной функции и некоторого правила следовать в их присутствии.

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

+0

Означает ли это, что компилятор не может кэшировать значение 'p-> some_flag' в регистре? Я не уверен в значении барьера памяти. Разум объясняет их немного? –

+0

Отредактирован ответ. Теперь понятно? –

+0

Да, спасибо. –

3

Как написано, компилятор может кэшировать результат, как вы описываете, или даже более тонким способом - путем помещения его в регистр. Вы можете предотвратить эту оптимизацию, сделав переменную volatile. Но это не обязательно достаточно - вы не должны кодировать его таким образом! Вы должны использовать переменные состояния как предписанные (блокировка, ожидание, разблокировка).

Попытка сделать работу вокруг библиотеки плохая, но она ухудшается. Возможно, прочитав статью Ганса Бёма по общей теме от PLDI 2005 («Нити не могут быть реализованы как библиотека»), или многие из его follow-on articles (которые приводят к работе над пересмотренной моделью памяти C++) поставят в вас страх Божий верните вас к прямому и узкому :).

+0

Я не могу прочитать эту бумагу, не заплатив деньги.Я слишком бедна, чтобы узнать =/ –

+1

Альтернативная бесплатная ссылка на технический отчет: http://www.hpl.hp.com/techreports/2004/HPL-2004-209.html – EmeryBerger

+0

Спасибо за ссылку. Я люблю читать документы Ганса. Они слишком веселы. Я потеряю час своей жизни. –

7

В общем случае, даже если многопоточность не была вовлечена и ваш цикл выглядел как:

void check_flag(foo_t* f) { 
    while(f->flag) 
     foo(&f->c, &f->m); 
} 

компилятор не смогут кэшировать тест f->flag. Это связано с тем, что компилятор не может знать, может ли функция (например, foo()) изменить любой объект, на который указывает f.

В особых случаях (foo() виден компилятору, и все указатели, передаваемые check_flag() известны не быть совмещенными или иначе видоизменяемыми foo()) компилятор может быть в состоянии оптимизировать проверку.

Однако pthread_cond_wait() должен быть реализован таким образом, чтобы предотвратить эту оптимизацию.

См Does guarding a variable with a pthread mutex guarantee it's also not cached?:

Вы также можете быть заинтересованы в ответ Стива Джессоп к: Can a C/C++ compiler legally cache a variable in a register across a pthread library call?

Но как далеко вы хотите, чтобы вопросы, поднятые бумаги Boehm в своей работе до вас. Насколько я могу судить, если вы хотите занять позицию, что pthreads не гарантирует/не может гарантировать, тогда вы по существу занимаете позицию, что pthreads бесполезно (или, по крайней мере, не дает никаких гарантий безопасности, которые Я думаю, что сокращение имеет тот же результат). Хотя это может быть справедливо в строгом смысле (как указано в документе), это также, вероятно, не является полезным ответом. Я не уверен, какой вариант у вас будет отличаться от pthreads на платформах на базе Unix.

+0

Хотел бы я ответить на два ответа, но я не могу. Я только что выбрал тот, у которого самый низкий представитель. Они были одинаково полезны, хотя! –

+1

Это лучший ответ, но мне нравится объяснение OP для принятия другого. :-) Для чего это необходимо, функции синхронизации pthread указаны как полные барьеры памяти. Как это реализовано, это ни одна из бизнес-приложений. это просто гарантировано. –

+0

«_pthread_cond_wait() должен быть реализован таким образом, чтобы предотвратить эту оптимизацию.» «Мне любопытно, как« pthread_cond_wait »может быть разумно реализована таким образом, который позволяет (некорректную) оптимизацию! – curiousguy

1

Летучие средства для этой цели. Опираясь на компилятор, чтобы узнать о методах кодирования pthread, мне кажется немного ошеломленным; В наши дни компиляторы довольно умны. Фактически, компилятор, вероятно, видит, что вы зацикливаете, чтобы протестировать переменную, и по этой причине не будет кэшировать ее в регистре, а не потому, что видит, что вы используете pthreads. Просто используйте volatile, если вы действительно заботитесь.

Вид забавной маленькой запиской. У нас есть VOLATILE #define, который либо «изменчивый» (когда мы думаем, что ошибка не может быть нашим кодом ...), либо пустой. Когда мы думаем, что у нас случился сбой из-за того, что оптимизатор нас убил, мы #define его «volatile», который ставит волатильность перед почти всем. Затем мы проверяем, не исчезла ли проблема. До сих пор ... ошибки были разработчиком, а не компилятором! кто бы мог подумать !? Мы разработали высокопроизводительную библиотеку потоков «non locking» и «non blocking». У нас есть тестовая платформа, которая забивает ее до тысячи гонок в секунду. Так что, мы никогда не обнаружили проблему, требующую изменчивости! Пока gcc никогда не кэшировал общую переменную в регистре. я ... мы тоже удивлены. Мы все еще ждем нашего шанса использовать volatile!

+0

Мне нравится рассказ =) есть upvote. –

+0

«Компиляторы довольно умны в эти дни». Когда компилятор предположил, что вызов функции не повлиял ни на что? – curiousguy

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