0

Я нахожу странное явление при использовании глобальной переменной в качестве параметра в C++. Вот код. Программа никогда не закончится при использовании -O2.Как запретить компиляцию C++ при использовании глобальной переменной в качестве параметров

#include <thread> 
#include <iostream> 

using namespace std; 

#define nthreads 2 
struct Global{ 
    int a[nthreads]; 
}; 

void func(int* a){ 
    while(*a == 0){ 
    } 
    cout << "done" << endl; 
} 

struct Global global; 

int main(){ 
    thread pid[nthreads]; 
    memset(global.a, 0, nthreads*sizeof(int)); 

    for(int i=0; i<nthreads; i++){ 
     pid[i] = std::thread(func, &global.a[i]); 
    } 

    sleep(2); 
    cout << "finished" << endl; 
    memset(global.a, 1, nthreads*sizeof(int)); 

    for(int i=0; i<nthreads; i++){ 
     pid[i].join(); 
    } 

    return 0; 
} 

При использовании -O0 все выглядит нормально. И распечатать переменную * a во время цикла, это все еще нормально.

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

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

На самом деле, я хочу сделать что-то вроде этого:

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

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

Что мне делать правильно?

+0

Там нет смысла использовать мьютекс лишь один из двух потоков - это в буквальном смысле механизм MUTual EXclusion. Не работает только с одной стороны. – MSalters

+0

На последней части то, что вы описываете, называется шаблоном производителя-потребителя. Вы можете посмотреть в [Boost LockFree] (http://www.boost.org/doc/libs/1_61_0/doc/html/lockfree.html), который предназначен для таких шаблонов. – MSalters

ответ

-1

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

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

Существует несколько способов сделать это правильно.

  1. Использование volatile, летучий классификатор явно указывает компилятору не оптимизировать на любой доступ к летучим объекта.

  2. A volatile Ключевое слово само по себе не всегда достаточно для правильного выполнения многопоточного исполнения. Хотя вы можете избежать этого здесь, правильный способ правильно выполнить многопоточное выполнение - это использовать мьютексы и переменные условия. Вы найдете полное описание в своей любимой книге на C++.

+2

['volatile' в основном бесполезен здесь] (http://stackoverflow.com/a/4558031/179910). Это не является необходимым и достаточным для достижения желаемого поведения. –

+0

@JerryCoffin: 'volatile' не позволяет компилятору оптимизировать цикл проверки. Это в основном то, что «volatile» делает: предотвращение оптимизации доступа к памяти, полезное для, например, регистр карты ввода-вывода с отображением памяти. Но это не гарантирует, что значение выбирается как обновленное значение: это может быть значение из кеша. –

+1

** - 1 ** Потому что совет «сделать это правильно. 1 Использовать' volatile' »неверно на многих (большинство?) Платформах. И не гарантируется стандартом. Андрей Александреску, я думаю, вместе с Хербом Саттером написал приличную статью о том, почему 'volatile' очень нелегка для многопоточности в переносном коде. У меня нет ссылки, извините, но google. Этот код мог бы использоваться, например, как «volatile». переменная 'atomic'. Или явная синхронизация. –

2

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

Один из способов сделать эту работу - использовать std::atomic.

Я только пробовал себя в многопоточности в современном C++, для целей обучения и исследований, но рабочий этого Кодекса:

#include <atomic> 
#include <array> 
#include <thread> 
#include <iostream> 
using namespace std; 

int const nthreads = 2; 

void func(atomic<int>* a) 
{ 
    while(a->load() == 0) 
    {} 
    cout << "done" << endl; 
} 

namespace global { 
    array<atomic<int>, nthreads> a;  // Zero-initialized automatically. 
} // namespace global 

auto main() 
    -> int 
{ 
    using namespace std::chrono_literals; 

    thread pid[nthreads]; 
    for(int i = 0; i < nthreads; ++i) 
    { 
     pid[i] = thread(func, &global::a[i]); 
    } 

    this_thread::sleep_for(2ms); 
    cout << "finished" << endl; 
    for(auto& item : global::a) 
    { 
     item = (int(unsigned(-1) & 0x0101010101010101)); 
    } 

    for(int i = 0; i < nthreads; ++i) { pid[i].join(); } 
}