LINUX.ORG.RU

История изменений

Исправление dzidzitop, (текущая версия) :

Читать про барьеры памяти, OoO execution, когерентность, оптимизации компилятора, C++ memory model до просветления.

У тебя в коде нужно placement new использовать, poor man. А проблема, которую ты хочешь показать, на amd64 без помощи компилятора не воспроизводится в принципе. Потому что на этой архитектуре sequentially consistent memory model.

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <stdexcept>

using namespace std;

void doLongThing() {
  cout << "starting long initialization of class MyTestTread" << endl;
  this_thread::sleep_for(chrono::seconds(2));
  cout << "long initialization of class MyTestTread has finished" << endl;
}

class MyTestTread {
public:
  MyTestTread(void) {}
  MyTestTread(const bool useMutex, mutex &_mutex) { // : m_mutex() {
    cout << "flag: " << flag << endl;
    if(useMutex) {
      lock_guard<mutex> lock(_mutex); // synchronising memory
      cout << "using mutex" << endl;
      doLongThing();
      flag = 2;
    } else {
      cout << "do not use mutex" << endl;
      doLongThing();
      flag = 2;
    }
    
    cout << "flag: " << flag << endl;
  }
  
  ~MyTestTread(){}

  void checkProblemAndPrintMessage(mutex &_mutex) {
    unique_lock<mutex> lock(_mutex);
    cout << "flag: " << flag << endl;
    if(flag == 2) {
      cout << "\t---== No probs man, it's all right! Congratulations! ===---" << endl;
    } else {
      cout << "\n\t---=== Poor poor man, something definetely went wrong... how flag could be " << flag << " in this moment? Only the way is we have undefined behaviour here. I think. ===---" << endl << endl;
    }
  }

private:
  // mutex m_mutex;
  int flag = 0;
};

mutex m;

void createAndInit(MyTestTread *t, const bool useMutex) {
  // this_thread::sleep_for(chrono::seconds(1));
  new (t) MyTestTread(useMutex, m);
}

void callCheckProblem(MyTestTread *t) {
  // just make sure order is always the same... 
  // if delete this line, results are about the same, but order of calling constructor and checkProblemAndPrintMessage can be different
  this_thread::sleep_for(chrono::seconds(1));
  t->checkProblemAndPrintMessage(m);
}

int main(int argc, char* argv[]) {
  string argv1(argv[1]);
  if(argc != 2 || (argv1 != "mutex" && argv1 != "nomutex")) {
    string p1("usage:\n\t- either\t");
    throw invalid_argument(p1 + argv[0] + " mutex\n\t- or\t\t" + argv[0] + " nomutex");
  }
  
  bool useMutex(argv1 == "mutex");
  MyTestTread t;
  
  thread t_createAndInit(createAndInit, &t, useMutex),
         t_callCheckProblem(callCheckProblem, &t);

  t_callCheckProblem.join();
  t_createAndInit.join();
}
$ ./a.out mutex
flag: 0
using mutex
starting long initialization of class MyTestTread
long initialization of class MyTestTread has finished
flag: 2
flag: 2
	---== No probs man, it's all right! Congratulations! ===---

Ещё раз - вся инфа есть в C++ memory model.

За выявление убогости стандартных плюсовых мьютексов благодарю. Только к святой роли конструкторов это не имеет никакого отношения. Фактически, такие мьютексы почти невозможно нормально передать между потоками (потому что нет базовых thread-safe механизмов синхронизации), и они обречены на то, чтобы быть убогим говном. На атомиках можно организовать безопасную передачу, но это каждый раз нужно городить небольшую кучку малочитабельного кода.

Исправление dzidzitop, :

Читать про барьеры памяти, OoO execution, когерентность, оптимизации компилятора, C++ memory model до просветления.

У тебя в коде нужно placement new использовать, poor man. А проблема, которую ты хочешь показать, на amd64 без помощи компилятора не воспроизводится в принципе. Потому что на этой архитектуре sequentially consistent memory model.

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <stdexcept>

using namespace std;

void doLongThing() {
  cout << "starting long initialization of class MyTestTread" << endl;
  this_thread::sleep_for(chrono::seconds(2));
  cout << "long initialization of class MyTestTread has finished" << endl;
}

class MyTestTread {
public:
  MyTestTread(void) {}
  MyTestTread(const bool useMutex, mutex &_mutex) { // : m_mutex() {
    cout << "flag: " << flag << endl;
    if(useMutex) {
      lock_guard<mutex> lock(_mutex); // synchronising memory
      cout << "using mutex" << endl;
      doLongThing();
      flag = 2;
    } else {
      cout << "do not use mutex" << endl;
      doLongThing();
      flag = 2;
    }
    
    cout << "flag: " << flag << endl;
  }
  
  ~MyTestTread(){}

  void checkProblemAndPrintMessage(mutex &_mutex) {
    unique_lock<mutex> lock(_mutex);
    cout << "flag: " << flag << endl;
    if(flag == 2) {
      cout << "\t---== No probs man, it's all right! Congratulations! ===---" << endl;
    } else {
      cout << "\n\t---=== Poor poor man, something definetely went wrong... how flag could be " << flag << " in this moment? Only the way is we have undefined behaviour here. I think. ===---" << endl << endl;
    }
  }

private:
  // mutex m_mutex;
  int flag = 0;
};

mutex m;

void createAndInit(MyTestTread *t, const bool useMutex) {
  // this_thread::sleep_for(chrono::seconds(1));
  new (t) MyTestTread(useMutex, m);
}

void callCheckProblem(MyTestTread *t) {
  // just make sure order is always the same... 
  // if delete this line, results are about the same, but order of calling constructor and checkProblemAndPrintMessage can be different
  this_thread::sleep_for(chrono::seconds(1));
  t->checkProblemAndPrintMessage(m);
}

int main(int argc, char* argv[]) {
  string argv1(argv[1]);
  if(argc != 2 || (argv1 != "mutex" && argv1 != "nomutex")) {
    string p1("usage:\n\t- either\t");
    throw invalid_argument(p1 + argv[0] + " mutex\n\t- or\t\t" + argv[0] + " nomutex");
  }
  
  bool useMutex(argv1 == "mutex");
  MyTestTread t;
  
  thread t_createAndInit(createAndInit, &t, useMutex),
         t_callCheckProblem(callCheckProblem, &t);

  t_callCheckProblem.join();
  t_createAndInit.join();
}
$ ./a.out mutex
flag: 0
using mutex
starting long initialization of class MyTestTread
long initialization of class MyTestTread has finished
flag: 2
flag: 2
	---== No probs man, it's all right! Congratulations! ===---

Ещё раз - вся инфа есть в C++ memory model.

За выявление убогости стандартных плюсовых мьютексов благодарю.

Исходная версия dzidzitop, :

Читать про барьеры памяти, OoO execution, когерентность, оптимизации компилятора, C++ memory model до просветления.

У тебя в коде нужно placement new использовать, poor man. А проблема, которую ты хочешь показать, на amd64 без помощи компилятора не воспроизводится в принципе. Потому что на этой архитектуре sequentially consistent memory model.

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <stdexcept>

using namespace std;

void doLongThing() {
  cout << "starting long initialization of class MyTestTread" << endl;
  this_thread::sleep_for(chrono::seconds(2));
  cout << "long initialization of class MyTestTread has finished" << endl;
}

class MyTestTread {
public:
  MyTestTread(void) {}
  MyTestTread(const bool useMutex, mutex &_mutex) { // : m_mutex() {
    cout << "flag: " << flag << endl;
    if(useMutex) {
      lock_guard<mutex> lock(_mutex); // synchronising memory
      cout << "using mutex" << endl;
      doLongThing();
      flag = 2;
    } else {
      cout << "do not use mutex" << endl;
      doLongThing();
      flag = 2;
    }
    
    cout << "flag: " << flag << endl;
  }
  
  ~MyTestTread(){}

  void checkProblemAndPrintMessage(mutex &_mutex) {
    unique_lock<mutex> lock(_mutex);
    cout << "flag: " << flag << endl;
    if(flag == 2) {
      cout << "\t---== No probs man, it's all right! Congratulations! ===---" << endl;
    } else {
      cout << "\n\t---=== Poor poor man, something definetely went wrong... how flag could be " << flag << " in this moment? Only the way is we have undefined behaviour here. I think. ===---" << endl << endl;
    }
  }

private:
  // mutex m_mutex;
  int flag = 0;
};

mutex m;

void createAndInit(MyTestTread *t, const bool useMutex) {
  // this_thread::sleep_for(chrono::seconds(1));
  new (t) MyTestTread(useMutex, m);
}

void callCheckProblem(MyTestTread *t) {
  // just make sure order is always the same... 
  // if delete this line, results are about the same, but order of calling constructor and checkProblemAndPrintMessage can be different
  this_thread::sleep_for(chrono::seconds(1));
  t->checkProblemAndPrintMessage(m);
}

int main(int argc, char* argv[]) {
  string argv1(argv[1]);
  if(argc != 2 || (argv1 != "mutex" && argv1 != "nomutex")) {
    string p1("usage:\n\t- either\t");
    throw invalid_argument(p1 + argv[0] + " mutex\n\t- or\t\t" + argv[0] + " nomutex");
  }
  
  bool useMutex(argv1 == "mutex");
  MyTestTread t;
  
  thread t_createAndInit(createAndInit, &t, useMutex),
         t_callCheckProblem(callCheckProblem, &t);

  t_callCheckProblem.join();
  t_createAndInit.join();
}

$ ./a.out mutex flag: 0 using mutex starting long initialization of class MyTestTread long initialization of class MyTestTread has finished flag: 2 flag: 2 ---== No probs man, it's all right! Congratulations! ===---

Ещё раз - вся инфа есть в C++ memory model.

За выявление убогости стандартных плюсовых мьютексов благодарю.