#c #multithreading #locking #thread-safety #boost-thread
#c #многопоточность #блокировка #безопасность потоков #boost-поток
Вопрос:
Я создал следующий пример программы для работы с потоками boost:
#pragma once
#include "boostthreadmutex.hpp"
#include <iostream>
class ThreadWorker
{
public:
ThreadWorker() {}
virtual ~ThreadWorker() {}
static void FirstCount(int threadId)
{
boost::mutex::scoped_lock(mutex_);
static int i = 0;
for(i = 1; i <= 30; i )
{
std::cout << i << ": Hi from thread: " << threadId << std::endl;
}
}
private:
boost::mutex mutex_;
};
основной класс:
// ThreadTest.cpp
#include "stdafx.h"
#include "boostthreadthread.hpp"
#include "ThreadWorker.h"
int _tmain(int argc, _TCHAR* argv[])
{
boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
boost::thread thread3(amp;ThreadWorker::FirstCount, 3);
thread1.join();
thread2.join();
thread3.join();
std::string input;
std::cout << "Press <enter> to finish...n";
std::getline( std::cin, input );
return 0;
}
Когда я запускаю это, я получаю следующий вывод:
1: Hi from thread: 1
1: Hi from thread: 3
2: Hi from thread: 3
...
Похоже, что сначала туда попадает поток 1, а затем поток 3. Разве scoped_lock не должен препятствовать другим потокам входить в этот раздел кода? Разве первый поток, который запускает FirstCount(), не должен завершаться?
Обновить
Одна вещь, которую я считаю неправильной в моем коде, — это эта строка:
boost::mutex::scoped_lock(mutex_);
Я думаю, что это должно быть как:
boost::mutex::scoped_lock xyz(mutex_);
Как только я это сделаю, он подает жалобу на то, что mutex_ не является статическим. Почему это сработало в первую очередь, я не уверен. Изменение mutex_ на static приводит к ошибке связывания:
1> ThreadWorker.obj : ошибка LNK2001: неразрешенный внешний символ «private: статический класс boost:: mutex ThreadWorker:: mutex_» (?mutex_ @ThreadWorker@@0Vmutex@boost@@A) 1>c:somethingThreadTestDebugThreadTest.exe : неустранимая ошибка LNK1120: 1 неразрешенные внешние
Все еще играю с этим.
Комментарии:
1. Этот код не компилируется. Является
mutex_
ли статический член в классе в реальном коде?2. по
static int i
сути, это общая глобальная переменная, и она не защищена (ее защищают разные мьютексы …)3. Функция статического класса не может получить доступ к нестатическому члену класса.
FirstCount
невозможно получить доступmutex_
, если первая является статической функцией, а вторая — нестатическим членом.4. Это вопрос для обсуждения, делаю ли я. Однако он инкапсулирует мьютекс.
5. @curiousguy Может быть, его причина заключалась в том, что ему нравится имя ThreadWorker, и поэтому он хочет создать объект и назвать его так. Может быть, он просто изучает потоки, думает, что они классные, и играет с ними, чтобы узнать, как они работают. Похоже на то, как вы были взволнованы, когда ребенок играл в песочнице и набил горсть песка в рот. Когда вы оглядываетесь на это сейчас, вы чувствуете себя грубым и глупым, но в тот момент это было что-то новое и захватывающее, и это было потрясающе, и никто не читал вам лекцию, чтобы объясниться. Его вопрос был ясен.
Ответ №1:
У вас две ошибки:
Прежде всего, как уже было замечено, mutex_
также должно быть статическим:
private:
static boost::mutex mutex_;
и, конечно, объявите его где-нибудь (желательно в файле .cpp!):
boost::mutex ThreadWorker::mutex_{};
Теперь, почему компилятор не жалуется? Ну, потому что вы на самом деле не создаете ограниченную блокировку с аргументом mutex_
здесь:
boost::mutex::scoped_lock(mutex_);
На самом деле это не вызовет нужный вам конструктор, а создаст (локальный) объект mutex_
, который имеет тип scoped_lock
и создается конструктором по умолчанию. Следовательно, проблем с компилятором нет. Вы должны изменить его на что-то вроде следующего:
boost::mutex::scoped_lock l{mutex_};
Теперь компилятор должен начать жаловаться на mutex_
Комментарии:
1. Это опечатка?:
boost::mutex::scoped_lock l{mutex_};
Почему фигурные скобки? Также почему здесь фигурные скобки?boost::mutex ThreadWorker::mutex_{};
спасибо2. Это не опечатка, это улучшенный синтаксис конструктора C 11, который предотвращает некоторые распространенные проблемы. Однако я не знаю, поддерживает ли Visual Studio это уже.
3. Интересно, я только что попробовал:
int(j);
и это работает, объявляя целое число j. Я не знал об этом. Немного страшноboost::mutex::scoped_lock(mutex_);
только потому, что он молча примет это.
Ответ №2:
У вас есть три отдельных объекта, и ни один из них не может видеть мьютекс_ другого, потому что этот элемент создается внутри каждого объекта.
Возможно, вы также хотели сделать mutex_ статическим?
Редактировать: если я сделаю мьютекс статическим и удалю статику из переменной i, то, похоже, он работает так, как я предполагаю, что вы это имели в виду. Кажется, происходит что-то вроде этого: каждый поток немедленно входит в цикл и не блокируется друг от друга из-за того, что мьютекс не является статическим. К тому времени, когда все они выводятся на консоль (я не могу вспомнить, существует ли взаимное исключение при записи в cout), и i увеличивается, все они видят статическое значение i как 30 и завершают работу.
Редактировать 2: Нет, все еще неверно, поскольку в некоторых прогонах все еще есть вкрапленные значения.
Редактировать 3: Причина, по которой он компилируется, заключается в том, что ваш scoped_lock является временным, что, по-видимому, отвлекает компилятор от того факта, что mutex_ должен быть статическим. Попробуйте вместо этого следующий код:
#include <iostream>
#include "boostthreadmutex.hpp"
#include "boostthreadthread.hpp"
class ThreadWorker
{
public:
ThreadWorker() {}
virtual ~ThreadWorker() {}
static void FirstCount(int threadId)
{
// Created object f here rather than temprary
boost::mutex::scoped_lock f(mutex_);
int i = 0; // Not static
for(i = 1; i <= 30; i )
{
std::cout << i << ": Hi from thread: " << threadId << std::endl;
}
}
private:
static boost::mutex mutex_; // Static
};
// Storage for static
boost::mutex ThreadWorker::mutex_;
int main(int argc, char* argv[])
{
boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
boost::thread thread3(amp;ThreadWorker::FirstCount, 3);
thread1.join();
thread2.join();
thread3.join();
std::string input;
std::cout << "Press <enter> to finish...n";
std::getline( std::cin, input );
return 0;
}
Комментарии:
1. Подумал то же самое, и я попытался сделать mutex_ статическим, но результат тот же.
2. Причина, по которой он проходит, заключается не в том, что компилятор оптимизирует временное значение (если бы он это сделал, многие RAII-коды потерпели бы неудачу). На самом деле он не создает временное значение, которое вы ожидаете от него создать.
3. 1 @tinman: спасибо за помощь в этом. В конечном счете, я думаю, что КиллианДС понял, что происходит, поэтому я выбрал его в качестве ответа
4. @KillianDS: спасибо, не знал, что это допустимый синтаксис для объявления переменной.
Ответ №3:
Этот код также компилируется с тем же компилятором?
Видите ли вы проблему с этим кодом? Попробуйте определить проблему, не компилируя ее.
класс C { public: static int f () { возвращает i; } int i; }; int main() { возвращает C::f(); }
Обновить: Я только что прочитал ваше обновление.
класс C { public: static int f () { возвращает i; } статический int i; }; int C::i = что угодно; int main() { возвращает C::f(); }
Ответ №4:
Изменил мой код на:
ThreadWorker.h:
#pragma once
#include "boostthreadmutex.hpp"
#include <iostream>
class ThreadWorker
{
public:
ThreadWorker();
virtual ~ThreadWorker();
static void FirstCount(int threadId);
private:
static boost::mutex mutex_;
};
ThreadWorker.cpp:
#include "stdafx.h"
#include "ThreadWorker.h"
boost::mutex ThreadWorker::mutex_;
ThreadWorker::ThreadWorker()
{
}
ThreadWorker::~ThreadWorker()
{
}
void ThreadWorker::FirstCount(int threadId)
{
boost::mutex::scoped_lock xyz(mutex_);
static int i = 0;
for(i = 1; i <= 30; i )
{
std::cout << i << ": Hi from thread: " << threadId << std::endl;
}
}
ThreadTest.cpp:
// ThreadTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "boostthreadthread.hpp"
#include "ThreadWorker.h"
int _tmain(int argc, _TCHAR* argv[])
{
boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
boost::thread thread3(amp;ThreadWorker::FirstCount, 3);
thread1.join();
thread2.join();
thread3.join();
std::string input;
std::cout << "Press <enter> to finish...n";
std::getline( std::cin, input );
return 0;
}
Внесенное мной изменение состояло в том, чтобы 1. разделить заголовок и файл cpp (изначально я делал это только для того, чтобы сделать мой пост более компактным) 2. присвоить переменной scoped_lock идентификатор и 3. сделать мьютекс статическим.
Теперь он работает, как и ожидалось, печатая по 30 строк из каждого потока последовательно. Что все еще ставит меня в тупик, так это то, почему код, скомпилированный ранее (поскольку tinman также смог скомпилировать мой исходный код).
Комментарии:
1. @curiousguy: статический int был преднамеренным. Я хотел протестировать блокировку общего ресурса. Я не уверен, что вы имеете в виду класс ThreadWorker.
2. @User: код перестал компилироваться, как только я изменил scoped_lock. Как указал KillianDS, он создавал новую локальную переменную, не ссылающуюся на член mutex_ класса. Поскольку он никогда не ссылался на класс mutex_, компилятору не нужно было создавать код для доступа к нему из статического метода, и поэтому ошибки компилятора не было.