Нашим процессорам разрешено переупорядочивать инструкцию, чтобы получить некоторые преимущества в производительности, но это может вызвать странное поведение. Я пытаюсь воспроизвести один из этих вопросов на основе Эта статья.
Это мой код:
int a,b;
int r1,r2;
mutex m1,m2;
void th1()
{
for(;;)
{
m1.lock();
a=1;
asm volatile("" ::: "memory");
r1=b;
m1.unlock();
}
}
void th2()
{
for(;;)
{
m2.lock();
b=1;
asm volatile("" ::: "memory");
r2=a;
m2.unlock();
}
}
int main()
{
int cnt{0};
thread thread1{th1};
thread thread2{th2};
thread1.detach();
thread2.detach();
for(int i=0;i<10000;i++)
{
m1.lock();
m2.lock();
a=b=0;
m1.unlock();
m2.unlock();
if(r1==0&&r2==0)
{
++cnt;
}
}
cout<<cnt<<" CPU reorders happened!\n";
}
Я использую мьютексы, чтобы быть уверенным, что «главный» поток не изменит ни nor, когда th1 или th2 выполняют свою работу, выходные данные выполнения постоянно меняются, это может быть 0, это может быть 10000 или случайный число от 0 до 10000.
Что-то в этом коде делает меня немного неловким, я не уверен, действительно ли он воспроизводит феномен переупорядочения процессора.
Из кода похоже, что единственный способ, которым r1 и r2 могут быть 0 в «если», из-за th1 и th2 установить их в значение из «a» и «b», которое в контексте th1 и th2 не может быть 0 из-за механизма блокировки, единственный способ, которым эти переменные равны 0, из-за переупорядочения команд, это правильно?
Спасибо
Ваша программа сильно отличается от той, что приведена в статье, которую вы цитировали на preshing.com. Программа preshing.com использует семафоры, где ваш использует мьютексы.
Мьютексы проще семафоров. Они дают только одну гарантию — только один поток одновременно может заблокировать мьютекс. То есть они могут использоваться только для взаимного исключения.
Программа preshing.com делает что-то со своими семафорами, чего нельзя сделать только с мьютексами: она синхронизирует циклы в трех потоках, так что все они работают в режиме блокировки. Thread1 и Thread2 каждый ожидают наверху своего цикла, пока main () не отпускает их, а затем main ждет внизу своего цикла, пока они не завершат свою работу. Тогда они все снова и снова.
Вы не можете сделать это с мьютексами. Что в вашей программе препятствует тому, чтобы main обходил свой цикл тысячи раз, прежде чем любой из двух других потоков вообще запустится? Ничего, кроме случайности. Также ничто не мешает Thread1 и / или Thread2 зацикливаться тысячи раз, пока main () блокируется, ожидая следующего отрезка времени.
Помните, семафор — это счетчик. Посмотрите внимательно на то, как семафоры в preshing.com увеличиваются и уменьшаются потоками, и вы увидите, как они синхронизируют потоки.
Я сделал ошибку, используя мьютексы вместо семафоров (спасибо большое Джеймсу), это правильно работающий код:
#include <mutex>
#include <condition_variable>
using namespace std;
class semaphore{
private:
mutex mtx;
condition_variable cv;
int cnt;
public:
semaphore(int count = 0):cnt(count){}
void notify()
{
unique_lock<mutex> lck(mtx);
++cnt;
cv.notify_one();
}
void wait()
{
unique_lock<mutex> lck(mtx);
while(cnt == 0){
cv.wait(lck);
}
--cnt;
}
};
int a,b;
int r1,r2;
semaphore s1,s2,s3;
void th1()
{
for(;;)
{
s1.wait();
a=1;
asm volatile("" ::: "memory");
r1=b;
s3.notify();
}
}
void th2()
{
for(;;)
{
s2.wait();
b=1;
asm volatile("" ::: "memory");
r2=a;
s3.notify();
}
}
int main()
{
int cnt{0};
thread thread1{th1};
thread thread2{th2};
thread1.detach();
thread2.detach();
for(int i=0;i<100000;i++)
{
a=b=0;
s1.notify();
s2.notify();
s3.wait();
s3.wait();
if(r1==0&&r2==0)
{
++cnt;
}
}
cout<<cnt<<" CPU reorders happened!\n";
}
Переупорядочение, похоже, правильно воспроизведено.