Почему оптимизация компилятора нарушает эту функциональность? (темы участвуют) - PullRequest
0 голосов
/ 13 декабря 2010

Есть поток, который нормально работает с циклом

void* Thread (void* nothing) {

    while(1) {
        // Sleep if requested
        if ( Sleep_c)
                Sys_Sleep (Sleep_c);

        Do_stuff();
        if (shutdown) {
            shutdown_ok = 1;
            break;
        }
    }
    return 0;
}

Функция в главном потоке, которая может уничтожить его,

 void Shutdown (void) {

    shutdown = 1;
    while (1) // Wait for it
        if (shutdown_ok) {
            shutdown = 0;
            break;
        }
 }

Теперь это прекрасно работает на отладчике. Но он застревает в цикле while (1) [функции выключения] в оптимизированном коде. Почему?

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

Ответы [ 5 ]

4 голосов
/ 13 декабря 2010

Поскольку компилятор не понимает, что shutdown_ok будет изменен вне функции в другом потоке.Возможно, компилятор выяснил, что shutdown_ok всегда будет иметь значение false в функции Shutdown(), и таким образом удалил оператор if.

void Shutdown (void)
{        
    shutdown = 1;
    while (1)
    {
        shutdown = 0;
    }
}

Можно пометить переменную как volatile, что служиткак подсказка компилятору о том, что переменная может быть изменена способами, которые не может предсказать компилятор.

Стандарт C ++ 7.1.5.1/8: [ Примечание: volatile является подсказкой для реализации, чтобы избежать агрессивной оптимизации, связанной с объектом, потому что значение объекта может быть изменено средствами, не обнаруживаемыми реализацией ... В общем, семантика volatile предназначена для того жев C ++, как и в C.]

Однако компиляторы могут заставить volatile переменные принимать определенное поведение, которое не указано в стандарте.Например, компиляторы Visual C ++ заставляют volatile переменные вести себя как блокировки памяти , но такое поведение фактически не гарантируется никаким стандартом.

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

3 голосов
/ 13 декабря 2010

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

Примечание

Люди предложат отключить volatile, и, вероятно, можно предположить, что запись в int атомарна. Это может помочь в большинстве случаев. Но даже в этом случае у вас могут возникнуть проблемы. На многоядерных машинах чтение и запись могут быть неожиданно переупорядочены, в результате чего вы пропустите важный сигнал. Intel имеет специальную инструкцию LOCK для решения этих случаев, но компилятор обычно не генерирует LOCK. В ARM у вас есть инструкция DMB, но она также вряд ли будет сгенерирована компилятором.

Итог при синхронизации потоков, используйте примитив ОС и не пытайтесь развернуть свой собственный. ВЫ ПОЛУЧИТЕ ЭТО НЕПРАВИЛЬНО

1 голос
/ 14 декабря 2010

Решением pthreads является использование условной переменной:

pthread_cond_t shutdown_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t shutdown_lock = PTHREAD_MUTEX_INITIALIZER;

void* Thread (void* nothing) {

    while(1) {
        // Sleep if requested
        if ( Sleep_c)
                Sys_Sleep (Sleep_c);

        Do_stuff();

        pthread_mutex_lock(&shutdown_lock);
        if (shutdown) {
            shutdown_ok = 1;
            pthread_cond_signal(&shutdown_cond);
            pthread_mutex_unlock(&shutdown_lock);
            break;
        }
        pthread_mutex_unlock(&shutdown_lock);
    }
    return 0;
}

void Shutdown (void)
{
    pthread_mutex_lock(&shutdown_lock); 
    shutdown = 1;
    while (!shutdown_ok)
        pthread_cond_wait(&shutdown_cond, &shutdown_lock);
    shutdown = 0;
    pthread_mutex_unlock(&shutdown_lock);
}

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

0 голосов
/ 13 декабря 2010

Я думаю, что модификатор 'volatile' для shutdown_ok должен избегать этого.Он сообщает компилятору, что переменная может быть изменена другим потоком, поэтому она всегда должна ссылаться на нее, а не использовать, например, регистр для хранения копии своего значения.

0 голосов
/ 13 декабря 2010

Каково определение выключения?Где это определено?Я подозреваю, что это должна быть переменная volatile .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...