История изменений
Исправление
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.
За выявление убогости стандартных плюсовых мьютексов благодарю.