I have a vector that I would like to access and work with from multiple threads. I have created an example below to illustrate the functionality that I would like to accomplish.
The goals are to be (1) high speed, (2) thread safe, and (3) if possible continue to use vectors as my larger project uses vectors all over the place and as such I would like to maintain that.
However, if I need to switch from vectors to something else then I am open to that as well.
The following example below compiles but crashes rather quickly because it is not thread safe.
Any ideas on how I can fix the example below which I can then apply to my larger project?
Thank you:
#include <vector>
#include <ctime>
#include <thread>
#include <iostream>
#include <random>
#include <atomic>
#include <algorithm>
enum EmployeeType {
HOURLY = 0,
SALARIED = 1,
COMMISSION = 2,
OTHER = 3
};
enum EmployeePosition {
SalesPerson = 0,
Cashier = 1,
Stocker = 2,
Janitor = 3,
AssistantManager = 4,
Manager = 5,
GeneralManager = 6,
Owner = 7
};
class Employee;
class Employee {
private:
float _TotalCostPerYear;
EmployeeType _TypeOfEmployee;
EmployeePosition _PositionOfEmployee;
protected:
public:
Employee() :_TotalCostPerYear(0.0f), _TypeOfEmployee(EmployeeType::HOURLY),
_PositionOfEmployee(EmployeePosition::SalesPerson){};
float GetTotalCost() { return _TotalCostPerYear; }
void SetTotalCost(float ValueToSet) { _TotalCostPerYear = ValueToSet; }
EmployeeType GetEmployeeType() { return _TypeOfEmployee; }
void SetEmployeeType(EmployeeType ValueToSet) { _TypeOfEmployee = ValueToSet; }
EmployeePosition GetEmployeePosition() { return _PositionOfEmployee; }
void SetEmployeePosition(EmployeePosition ValueToSet) { _PositionOfEmployee = ValueToSet; }
};
std::vector <Employee> AllEmployees;
std::thread* AddEmployeesThread;
std::thread* RemoveEmployeesThread;
std::thread* CalculateEmploymentCostsThread;
std::thread* PrintTotalsThread;
std::atomic<bool> ContinueProcessing = true;
std::atomic<float> TotalSalaryCosts = 0.0f;
std::uniform_int_distribution<int>* RandDistTypeOfEmployee;
std::uniform_int_distribution<int>* RandDistPositionOfEmployee;
std::uniform_real_distribution<float>* RandDistSalaryOfEmployee;
std::mt19937* RandomNumberGenerator;
time_t rawtime;
struct tm timeinfo;
void RandomAddEmployees();
void RandomRemoveEmployees();
void CalculateEmployementCosts();
void PrintTotals();
void RandomAddEmployees() {
while (ContinueProcessing) {
Employee NewEmployee;
NewEmployee.SetEmployeePosition((EmployeePosition)(*RandDistPositionOfEmployee)(*RandomNumberGenerator));
NewEmployee.SetEmployeeType((EmployeeType)(*RandDistTypeOfEmployee)(*RandomNumberGenerator));
NewEmployee.SetTotalCost((*RandDistSalaryOfEmployee)(*RandomNumberGenerator));
AllEmployees.push_back(NewEmployee);
}
}
void RandomRemoveEmployees() {
while (ContinueProcessing) {
EmployeePosition PositionToRemove = (EmployeePosition)(*RandDistPositionOfEmployee)(*RandomNumberGenerator);
static const auto is_position_erasable = [&PositionToRemove](Employee& E) { return E.GetEmployeePosition() == PositionToRemove; };
AllEmployees.erase(std::remove_if(AllEmployees.begin(), AllEmployees.end(), is_position_erasable), AllEmployees.end());
EmployeeType TypeToRemove = (EmployeeType)(*RandDistTypeOfEmployee)(*RandomNumberGenerator);
static const auto is_type_erasable = [&TypeToRemove](Employee& E) { return E.GetEmployeeType() == TypeToRemove; };
AllEmployees.erase(std::remove_if(AllEmployees.begin(), AllEmployees.end(), is_position_erasable), AllEmployees.end());
}
}
void CalculateEmployementCosts() {
while (ContinueProcessing) {
float RunningTotal = 0.0f;
for (unsigned int i = 0; i < AllEmployees.size(); ++i) {
RunningTotal += AllEmployees[i].GetTotalCost();
}
TotalSalaryCosts = RunningTotal;
}
}
void PrintTotals() {
while (ContinueProcessing) {
time(&rawtime);
localtime_s(&timeinfo, &rawtime);
if ((timeinfo.tm_sec % 5) == 0) {
std::cout << "\n\nIn total there are " << AllEmployees.size() << " employees with a total cost of " << TotalSalaryCosts << " to the company.";
}
}
}
int main(int argc, char** argv) {
time(&rawtime);
localtime_s(&timeinfo, &rawtime);
RandomNumberGenerator = new std::mt19937((unsigned int)timeinfo.tm_sec);
RandDistTypeOfEmployee = new std::uniform_int_distribution<int>(0, 3);
RandDistPositionOfEmployee = new std::uniform_int_distribution<int>(0, 7);
RandDistSalaryOfEmployee = new std::uniform_real_distribution<float>(35000.0f, 300000.0f);
std::cout << "Welcome to the crude employment simulation program. Press enter to get started.";
std::cout << "\n\nNote that once the program starts you can press any key to stop the simulation.\n";
std::cin.get();
AddEmployeesThread = new std::thread(RandomAddEmployees);
RemoveEmployeesThread = new std::thread(RandomRemoveEmployees);
CalculateEmploymentCostsThread = new std::thread(CalculateEmployementCosts);
PrintTotalsThread = new std::thread(PrintTotals);
std::cin.get();
std::cout << "\n\nExiting the simulation.";
ContinueProcessing = false;
AddEmployeesThread->join();
RemoveEmployeesThread->join();
CalculateEmploymentCostsThread->join();
PrintTotalsThread->join();
delete AddEmployeesThread;
delete RemoveEmployeesThread;
delete CalculateEmploymentCostsThread;
delete PrintTotalsThread;
delete RandDistSalaryOfEmployee;
delete RandDistPositionOfEmployee;
delete RandDistTypeOfEmployee;
}
You need to protect access to your AllEmployees variable so that only one thread can access it at any time. You can use a std::mutex for protection, locking it with a std::lock_guard<std::mutex> where necessary. First, add this include file:
#include <mutex>
Next, define a mutex — you can add the following definition just above your AllEmployees variable:
std::mutex AllEmpMtx;
Then, in all functions that access or modify AllEmployees, lock the mutex before any such operation, like this:
std::lock_guard<std::mutex> lock(AllEmpMtx);
For example, in the RandomAddEmployees function, you should add the lock_guard just above the call to AllEmployees.push_back(NewEmployee).
When the std::lock_guard<std::mutex> instance goes out of scope, its destructor will unlock the mutex.
By the way, you seem to be using scoped enumerations for EmployeeType and EmployeePosition. The C++11 standard requires them to be defined with enum class, not just enum.
Related
I'm working on C++ code for a 'manager' class that runs a process in multiple member objects in separate threads and returns a set of values. I see three basic ways I can implement this:
create each thread with the member function for the relevant object and a callback mechanism to return the values;
provide an auxiliary function in the manager class that calls the member function for a specified object and create separate threads with this auxiliary function; or
create each thread with the member function for the relevant object (as in method #1) and pass pointers to variables to hold the return values.
My question is, are there compelling reasons to choose one method over the other, in terms of performance or other factors, keeping in mind the fact that the actual application would have an array or vector of an indeterminate number of objects (unlike the examples below)?
An example of the first method would look like this:
#include <thread>
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
typedef function<void(string, int)> Callback;
class Processor {
private:
string name;
Callback cb_func;
public:
Processor(string nme, Callback f) : name(nme), cb_func(f) { }
void do_stuff(int lim) { cb_func(name, rand() % lim); }
};
class Manager {
private:
Processor *p0, *p1;
public:
Manager() {
p0 = new Processor("lizard", std::bind(&Manager::report, this, _1, _2));
p1 = new Processor("ferret", std::bind(&Manager::report, this, _1, _2));
}
~Manager() {
delete p0;
delete p1;
}
void manage() {
thread t0 = thread(&Processor::do_stuff, p0, 100);
thread t1 = thread(&Processor::do_stuff, p1, 100);
t0.join();
t1.join();
}
void report(string source, int value) {
cout << source << " reports " << value << endl;
}
};
int main() {
Manager the_dude;
the_dude.manage();
return 0;
}
An example of the second method would look like this:
#include <thread>
#include <iostream>
using namespace std;
class Processor {
private:
string name;
public:
Processor(string nme) : name(nme) { }
int do_stuff(int lim) { return rand() % lim; }
string get_name() { return name; }
};
class Manager {
private:
Processor *p0, *p1;
public:
Manager() {
p0 = new Processor("lizard");
p1 = new Processor("ferret");
}
~Manager() {
delete p0;
delete p1;
}
void work(Processor *p, int lim) {
cout << p->get_name() << " reports " << p->do_stuff(lim) << endl;
}
void manage() {
thread t0 = thread(&Manager::work, this, p0, 100);
thread t1 = thread(&Manager::work, this, p1, 100);
t0.join();
t1.join();
}
};
int main() {
Manager the_dude;
the_dude.manage();
return 0;
}
And an example of the third method would look like this:
#include <thread>
#include <iostream>
using namespace std;
class Processor {
private:
string name;
public:
Processor(string nme) : name(nme) { }
void do_stuff(int lim, string *nme, int *val) { *nme = name; *val = rand() % lim; }
};
class Manager
{
private:
Processor *p0, *p1;
string s0, s1;
int v0, v1;
public:
Manager() {
p0 = new Processor("lizard");
p1 = new Processor("ferret");
}
~Manager() {
delete p0;
delete p1;
}
void manage() {
thread t0 = thread(&Processor::do_stuff, p0, 100, &s0, &v0);
thread t1 = thread(&Processor::do_stuff, p1, 100, &s1, &v1);
t0.join();
t1.join();
report(s0, v0);
report(s1, v1);
}
void report(string source, int value) {
cout << source << " reports " << value << endl;
}
};
int main()
{
Manager the_dude;
the_dude.manage();
return 0;
}
Assume that there is a class which contains some data and calculates some results given queries, and the queries take a relatively large amount of time.
An example class (everything dummy) is:
#include <vector>
#include <numeric>
#include <thread>
struct do_some_work
{
do_some_work(std::vector<int> data)
: _data(std::move(data))
, _current_query(0)
, _last_calculated_result(0)
{}
void update_query(size_t x) {
if (x < _data.size()) {
_current_query = x;
recalculate_result();
}
}
int get_result() const {
return _last_calculated_result;
}
private:
void recalculate_result() {
//dummy some work here
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
_last_calculated_result = std::accumulate(_data.cbegin(), _data.cbegin() + _current_query, 0);
}
std::vector<int> const _data;
size_t _current_query;
int _last_calculated_result;
};
and this can be used in the main code like:
#include <algorithm>
int main()
{
//make some dummy data
std::vector<int> test_data(20, 0);
std::iota(test_data.begin(), test_data.end(), 0);
{
do_some_work work(test_data);
for (size_t i = 0; i < test_data.size(); ++i) {
work.update_query(i);
std::cout << "result = {" << i << "," << work.get_result() << "}" << std::endl;
}
}
}
The above will wait in the main function a lot.
Now, assuming we want to run this querying in a tight loop (say GUI) and only care about about getting a "recent" result quickly when we query.
So, we want to move the work to a separate thread which calculates the results, and updates it, and when we get result, we get the last calculated one. That is, we want to change do_some_work class to do its work on a thread, with minimal changes (essentially find a pattern of changes that can be applied to (mostly) any class of this type).
My stab at this is the following:
#include <vector>
#include <numeric>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <iostream>
struct do_lots_of_work
{
do_lots_of_work(std::vector<int> data)
: _data(std::move(data))
, _current_query(0)
, _last_calculated_result(0)
, _worker()
, _data_mtx()
, _result_mtx()
, _cv()
, _do_exit(false)
, _work_available(false)
{
start_worker();
}
void update_query(size_t x) {
{
if (x < _data.size()) {
std::lock_guard<std::mutex> lck(_data_mtx);
_current_query = x;
_work_available = true;
_cv.notify_one();
}
}
}
int get_result() const {
std::lock_guard<std::mutex> lck(_result_mtx);
return _last_calculated_result;
}
~do_lots_of_work() {
stop_worker();
}
private:
void start_worker() {
if (!_worker.joinable()) {
std::cout << "starting worker..." << std::endl;
_worker = std::thread(&do_lots_of_work::worker_loop, this);
}
}
void stop_worker() {
std::cout << "worker stopping..." << std::endl;
if (_worker.joinable()) {
std::unique_lock<std::mutex> lck(_data_mtx);
_do_exit = true;
lck.unlock();
_cv.notify_one();
_worker.join();
}
std::cout << "worker stopped" << std::endl;
}
void worker_loop() {
std::cout << "worker started" << std::endl;
while (true) {
std::unique_lock<std::mutex> lck(_data_mtx);
_cv.wait(lck, [this]() {return _work_available || _do_exit; });
if (_do_exit) { break; }
if (_work_available) {
_work_available = false;
int query = _current_query; //take local copy
lck.unlock(); //unlock before doing lots of work.
recalculate_result(query);
}
}
}
void recalculate_result(int query) {
//dummy lots of work here
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
int const result = std::accumulate(_data.cbegin(), _data.cbegin() + query, 0);
set_result(result);
}
void set_result(int result) {
std::lock_guard<std::mutex> lck(_result_mtx);
_last_calculated_result = result;
}
std::vector<int> const _data;
size_t _current_query;
int _last_calculated_result;
std::thread _worker;
mutable std::mutex _data_mtx;
mutable std::mutex _result_mtx;
std::condition_variable _cv;
bool _do_exit;
bool _work_available;
};
and the usage is (example):
#include <algorithm>
int main()
{
//make some dummy data
std::vector<int> test_data(20, 0);
std::iota(test_data.begin(), test_data.end(), 0);
{
do_lots_of_work work(test_data);
for (size_t i = 0; i < test_data.size(); ++i) {
work.update_query(i);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "result = {" << i << "," << work.get_result() << "}" << std::endl;
}
}
}
This seems to work, giving the last result, not stopping the main function etc.
But, this looks a LOT of changes are required to add a worker thread to a simple class like do_some_work. Items like two mutexes (one for the worker/main interaction data, and one for the result), one condition_variable, one more-work-available flag and one do-exit flag, that is quite a bit. I guess we don't want an async kind of mechanism because we don't want to potentially launch a new thread every time.
Now, I am not sure if there is a MUCH simpler pattern to make this kind of change, but it feels like there should be. A kind of pattern that can be used to off-load work to a thread.
So finally, my question is, can do_some_work be converted into do_lots_of_work in a much simpler way than the implementation above?
Edit (Solution 1) ThreadPool based:
Using a threadpool, the worker loop can be skipped, we need two mutexes, for result and query. Lock in updating query, Lock in getting result, Both lock in recalculate (take a local copy of a query, and write to result).
Note: Also, when pushing work on the queue, as we do not care about the older results, we can clear the work queue.
Example implementation (using the CTPL threadpool)
#include "CTPL\ctpl_stl.h"
#include <vector>
#include <mutex>
struct do_lots_of_work_with_threadpool
{
do_lots_of_work_with_threadpool(std::vector<int> data)
: _data(std::move(data))
, _current_query(0)
, _last_calculated_result(0)
, _pool(1)
, _result_mtx()
, _query_mtx()
{
}
void update_query(size_t x) {
if (x < _data.size()) {
std::lock_guard<std::mutex> lck(_query_mtx);
_current_query = x;
}
_pool.clear_queue(); //clear as we don't want to calculate any out-date results.
_pool.push([this](int id) { recalculate_result(); });
}
int get_result() const {
std::lock_guard<std::mutex> lck(_result_mtx);
return _last_calculated_result;
}
private:
void recalculate_result() {
//dummy some work here
size_t query;
{
std::lock_guard<std::mutex> lck(_query_mtx);
query = _current_query;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
int result = std::accumulate(_data.cbegin(), _data.cbegin() + query, 0);
{
std::lock_guard<std::mutex> lck(_result_mtx);
_last_calculated_result = result;
}
}
std::vector<int> const _data;
size_t _current_query;
int _last_calculated_result;
ctpl::thread_pool _pool;
mutable std::mutex _result_mtx;
mutable std::mutex _query_mtx;
};
Edit (Solution 2) With ThreadPool and Atomic:
This solution changes the shared variables to atomic, and so we do not need any mutexes and do not have to consider taking/releasing locks etc. This is much simpler and very close to the original class (of course assumes a threadpool type exists somewhere as it is not part of the standard).
#include "CTPL\ctpl_stl.h"
#include <vector>
#include <mutex>
#include <atomic>
struct do_lots_of_work_with_threadpool_and_atomics
{
do_lots_of_work_with_threadpool_and_atomics(std::vector<int> data)
: _data(std::move(data))
, _current_query(0)
, _last_calculated_result(0)
, _pool(1)
{
}
void update_query(size_t x) {
if (x < _data.size()) {
_current_query.store(x);
}
_pool.clear_queue(); //clear as we don't want to calculate any out-date results.
_pool.push([this](int id) { recalculate_result(); });
}
int get_result() const {
return _last_calculated_result.load();
}
private:
void recalculate_result() {
//dummy some work here
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
_last_calculated_result.store(std::accumulate(_data.cbegin(), _data.cbegin() + _current_query.load(), 0));
}
std::vector<int> const _data;
std::atomic<size_t> _current_query;
std::atomic<int> _last_calculated_result;
ctpl::thread_pool _pool;
};
I'm messing around with C++ by making a console game. I have a superclass called "WeaponType." Within this class is a static method that creates all of the types of weapons that I'll need. The child class "Weapon" will inherit from WeaponType. (I'm new to inheritance, so forgive my ignorance in advance.)
The way I see the code now, I'll have to declare a new instance of the WeaponType superclass every time I create an instance of the Weapon subclass. This is a problem because I don't want to create any more instances of the WeaponType superclass that are all initialized on the HEAP in the static method that creates them.
I'm either looking for a way to delete every new instance of the WeaponType superclass after doing what I need to with the object. (For example, if make another instance of WeaponType that specifies it's supposed to be the "melee" object and I have already created an instance called "melee", I want to be able to "migrate" the WeaponType to the original object and delete the new object).
The code I have is not complete, but you should be able to get the idea of what I want.
Here's most of the code for WeaponType:
<!-- WeaponType.h -->
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<map>
class WeaponType
{
private:
static std::map<int, WeaponType*> weapTypeAddresses;
/* Private setters */
static void setWeapTypeAddresses(const std::vector<WeaponType*>&);
protected:
double reloadSpeed;
int weapTypeID, ammoPerShot, maxAmmo;
std::string weapTypeName;
public:
/* Constructors */
WeaponType(int, double, int, int);
WeaponType(int);
WeaponType();
static void MakeWeaponTypes();
/* Destructor */
~WeaponType();
/* Converters */
static int convTo_WeapTypeID(const std::string&);
/* Getters */
double getReloadSpeed() const;
int getWeapTypeID() const;
int getAmmoPerShot()const;
int getMaxAmmo() const;
std::string getWeapTypeName() const;
static WeaponType* getWeapTypeAddress(int);
};
<!-- WeaponType.cpp -->
#include "WeaponType.h"
/* Global Variables */
enum WeaponClasses {
meleeE, pistolE, machineGunE, shotgunE, sniperRifleE, explosiveE,
WeaponClasses_SizeE = explosiveE + 1
};
std::vector<std::string> weaponClassNames =
{ "melee", "pistol", "machineGun", "shotgun", "sniperRifle", "explosive" };
enum reloadSpeeds
{
meleeRS = 0, pistolRS = 2, machineGunRS = 6,
shotgunRS = 5, sniperRifleRS = 7, explosiveRS = 30
};
enum ammoPerShots
{
meleeAPS = 0, pistolAPS = 1, machineGunAPS = 4,
shotgunAPS = 2, sniperRifleAPS = 1, explosiveAPS = 1
};
enum maxAmmo
{
meleeMA = 1, pistolMA = 15, machineGunMA = 50,
shotgunMA = 20, sniperRifleMA = 10, explosiveMA = 3
};
bool weaponsMade = false;
/*******************
* Private Setters *
*******************/
// Maps weapTypeID to its pointer
void WeaponType::setWeapTypeAddresses (const std::vector<WeaponType*>& weapTypePtrs)
{
weapTypeAddresses[meleeE] = weapTypePtrs[meleeE];
weapTypeAddresses[pistolE] = weapTypePtrs[pistolE];
weapTypeAddresses[shotgunE] = weapTypePtrs[shotgunE];
weapTypeAddresses[machineGunE] = weapTypePtrs[machineGunE];
weapTypeAddresses[sniperRifleE] = weapTypePtrs[sniperRifleE];
weapTypeAddresses[explosiveE] = weapTypePtrs[explosiveE];
}
/****************
* Constructors *
****************/
WeaponType::WeaponType
(int weapTypeID, double reloadSpeed, int ammoPerShot, int maxAmmo) :
reloadSpeed(reloadSpeed), ammoPerShot(ammoPerShot), maxAmmo(maxAmmo)
{
// Makes sure weapTypeID is a valid int within WeaponClasses
try
{
if (weapTypeID >= 0 && weapTypeID < WeaponClasses_SizeE) { this->weapTypeID = weapTypeID; }
else { throw weapTypeID; }
}
catch (int e)
{
std::cerr << weapTypeID << " is not a valid weapTypeID" << std::endl;
std::cerr << "Setting weapTypeID to 0" << std::endl;
this->weapTypeID = meleeE;
}
weapTypeName = weaponClassNames[weapTypeID];
}
/* This is the important constructor that needs to delete the object
that it's constructing */
WeaponType::WeaponType(int weapClassID)
{
try
{
// I need to delete the object that's being created somewhere in here
switch (weapClassID)
{
case meleeE:
*this = *WeaponType::getWeapTypeAddress(meleeE);
break;
case pistolE:
*this = *WeaponType::getWeapTypeAddress(pistolE);
break;
case machineGunE:
*this = *WeaponType::getWeapTypeAddress(machineGunE);
break;
case shotgunE:
*this = *WeaponType::getWeapTypeAddress(shotgunE);
break;
case sniperRifleE:
*this = *WeaponType::getWeapTypeAddress(sniperRifleE);
break;
case explosiveE:
*this = *WeaponType::getWeapTypeAddress(explosiveE);
break;
default:
throw weapClassID;
break;
}
}
catch (int e)
{
std::cerr
<< '\"' << e << "\" is not a valid "
<< "weapClassID in WeaponType::WeapType(int)"
<< std::endl;
std::cerr << "Calling destructor" << std::endl;
delete this;
}
}
// Empty constructor for reasignment
WeaponType::WeaponType() {}
void WeaponType::MakeWeaponTypes()
{
if (!weaponsMade)
{
WeaponType * melee = new WeaponType( meleeE, meleeRS, meleeAPS, meleeMA ),
* pistol = new WeaponType( pistolE, pistolRS, pistolAPS, pistolMA ),
* machineGun = new WeaponType( machineGunE, machineGunRS, machineGunAPS, machineGunMA ),
* shotgun = new WeaponType( shotgunE, shotgunRS, shotgunAPS, shotgunMA ),
* sniperRifle = new WeaponType( sniperRifleE, sniperRifleRS, sniperRifleAPS, sniperRifleMA ),
* explosive = new WeaponType( explosiveE, explosiveRS, explosiveAPS, explosiveMA );
// Sets all the pointers to weapTypeAddresses map
std::vector<WeaponType*> weapTypePtrs =
{
melee, pistol, machineGun,
shotgun, sniperRifle, explosive
};
WeaponType::setWeapTypeAddresses(weapTypePtrs);
weaponsMade = true;
}
}
/*****************
* Other Methods *
*****************/
/* Destructor */
WeaponType::~WeaponType()
{
std::cout << "WeaponTypeID \"" << this->weapTypeID << "\" is being deleted" << std::endl;
}
/**************
* Converters *
**************/
// Converts weapTypeName to weapTypeID
int WeaponType::convTo_WeapTypeID(const std::string& className)
{
WeaponClasses weapClass;
// Makes sure className is valid
try
{
int errorCounter = 0, errorMax = 0;
for (int i = 0; i < WeaponClasses_SizeE; i++)
{
if (className != weaponClassNames[i]) { errorCounter++; }
errorMax++; // Always increases errorMax by 1
}
if (errorCounter == errorMax) { throw 0; }
}
catch (int e)
{
std::cerr << "className parameter is invalid" << std::endl;
std::cerr << "Returning \"" << weaponClassNames[0] << "\"" << std::endl;
return meleeE;
}
// Maps the weapTypeName to weapTypeID
std::map<std::string, WeaponClasses> Weap_ID;
Weap_ID[weaponClassNames[meleeE]] = meleeE;
Weap_ID[weaponClassNames[pistolE]] = pistolE;
Weap_ID[weaponClassNames[machineGunE]] = machineGunE;
Weap_ID[weaponClassNames[shotgunE]] = shotgunE;
Weap_ID[weaponClassNames[sniperRifleE]] = sniperRifleE;
Weap_ID[weaponClassNames[explosiveE]] = explosiveE;
weapClass = Weap_ID[className];
return weapClass;
}
/***********
* Getters *
***********/
double WeaponType::getReloadSpeed() const { return reloadSpeed; }
int WeaponType::getWeapTypeID() const { return weapTypeID; }
int WeaponType::getAmmoPerShot() const { return ammoPerShot; }
int WeaponType::getMaxAmmo() const { return maxAmmo; }
std::string WeaponType::getWeapTypeName() const { return weapTypeName; }
WeaponType* WeaponType::getWeapTypeAddress(int weapClassID)
{
// Makes sure weapClassID is valid input
try
{
switch (weapClassID)
{
case 0:
break;
case 1:
break;
case 2:
break;
default:
throw weapClassID;
}
}
catch (int e)
{
std::cerr
<< '\"' << e << "\" is not a valid "
<< "weapClassID in WeaponType::getWeapTypeAddress(int)"
<< std::endl;
std::cerr << "Returning " << weapTypeAddresses[meleeE] << std::endl;
return weapTypeAddresses[meleeE];
}
return weapTypeAddresses[weapClassID];
}
From Weapon.cpp (not yet created), I'd have something like the following:
(Again, please keep in mind I'm new to inheritance and may have incorrect code.)
<!-- Pre-file of Weapon.cpp -->
#include "Weapon.h"
#include "WeaponType.h"
Weapon::Weapon(std::string weapName, double damage, int ammo, int weapTypeID) :
WeaponType::WeaponType(weapTypeID)
{
this->damage = damage;
this->ammo = ammo;
}
And, finally, a bare-bones main method
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include "WeaponType.h"
#include "Weapon.h"
using namespace std;
int main()
{
string gunName = "pistolGun";
double gunDamage = 10.0;
int gunAmmo = 20;
int weapTypeID = 1;
// Creates a pistolGun object.
Weapon pistolGun (gunGame, gunDamage, gunAmmo, weapTypeID);
// The goal is to get the WeaponType* of pistolGun to equal &pistol from WeaponType.
// That would mean these who lines of code output the same address.
// Also note: I know this code to reference these addresses is incorrect
cout << "Address of WeaponType attributes of pistolGun: " << &pistolGun.WeaponTypeAttribs << endl;
cout << "Address of pistol WeaponType: " &pistol << endl;
}
From my understanding, you're using WeaponType to store all the characteristics, whereas a Weapon represents an actual object in the game, so it has a type but it also has things like ammo or a position in the world and things like this.
Instead of modelling this relationship as Weapon is-a WeaponType, consider modelling it as a Weapon has-a WeaponType. Cutting down your linked code to something reasonable, an approach like this might be better:
#include<string>
#include<memory>
class WeaponType
{
public:
double reloadSpeed;
int weapTypeID, ammoPerShot, maxAmmo;
std::string weapTypeName;
};
class Weapon
{
public:
explicit Weapon(std::shared_ptr<const WeaponType> type)
: type(std::move(type))
{
ammo = this->type->maxAmmo;
}
const std::shared_ptr<const WeaponType> type;
int ammo;
};
int main()
{
// make the weapon types
auto pistolType = std::make_shared<WeaponType>(WeaponType{ 1.0, 0, 1, 20, "pistol" });
auto machineGunType = std::make_shared<WeaponType>(WeaponType{ 5.0, 1, 1, 100, "machine gun" });
// ...etc
// Create some actual weapon objects out of those types
auto pistolGun1 = Weapon{ pistolType };
auto pistolGun2 = Weapon{ pistolType };
auto machineGun1 = Weapon{ machineGunType };
auto machineGun2 = Weapon{ machineGunType };
}
I have a list of objects, each object has member variables which are calculated by an "update" function. I want to update the objects in parallel, that is I want to create a thread for each object to execute it's update function.
Is this a reasonable thing to do? Any reasons why this may not be a good idea?
Below is a program which attempts to do what I described, this is a complete program so you should be able to run it (I'm using VS2015). The goal is to update each object in parallel. The problem is that once the update function completes, the thread throws an "resource dead lock would occur" exception and aborts.
Where am I going wrong?
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
class Object
{
public:
Object(int sleepTime, unsigned int id)
: m_pSleepTime(sleepTime), m_pId(id), m_pValue(0) {}
void update()
{
if (!isLocked()) // if an object is not locked
{
// create a thread to perform it's update
m_pThread.reset(new std::thread(&Object::_update, this));
}
}
unsigned int getId()
{
return m_pId;
}
unsigned int getValue()
{
return m_pValue;
}
bool isLocked()
{
bool mutexStatus = m_pMutex.try_lock();
if (mutexStatus) // if mutex is locked successfully (meaning it was unlocked)
{
m_pMutex.unlock();
return false;
}
else // if mutex is locked
{
return true;
}
}
private:
// private update function which actually does work
void _update()
{
m_pMutex.lock();
{
std::cout << "thread " << m_pId << " sleeping for " << m_pSleepTime << std::endl;
std::chrono::milliseconds duration(m_pSleepTime);
std::this_thread::sleep_for(duration);
m_pValue = m_pId * 10;
}
m_pMutex.unlock();
try
{
m_pThread->join();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl; // throws "resource dead lock would occur"
}
}
unsigned int m_pSleepTime;
unsigned int m_pId;
unsigned int m_pValue;
std::mutex m_pMutex;
std::shared_ptr<std::thread> m_pThread; // store reference to thread so it doesn't go out of scope when update() returns
};
typedef std::shared_ptr<Object> ObjectPtr;
class ObjectManager
{
public:
ObjectManager()
: m_pNumObjects(0){}
void updateObjects()
{
for (int i = 0; i < m_pNumObjects; ++i)
{
m_pObjects[i]->update();
}
}
void removeObjectByIndex(int index)
{
m_pObjects.erase(m_pObjects.begin() + index);
}
void addObject(ObjectPtr objPtr)
{
m_pObjects.push_back(objPtr);
m_pNumObjects++;
}
ObjectPtr getObjectByIndex(unsigned int index)
{
return m_pObjects[index];
}
private:
std::vector<ObjectPtr> m_pObjects;
int m_pNumObjects;
};
void main()
{
int numObjects = 2;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(numObjects);
for (int i = 0; i < numObjects; ++i)
objectSleepTimes.push_back(rand());
ObjectManager mgr;
// Create some objects
for (int i = 0; i < numObjects; ++i)
mgr.addObject(std::make_shared<Object>(objectSleepTimes[i], i));
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (int i = 0; i < numObjects; ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.updateObjects();
int numCompleted = 0; // number of objects which finished updating
while (numCompleted != numObjects)
{
for (int i = 0; i < numObjects; ++i)
{
auto objectRef = mgr.getObjectByIndex(i);
if (!objectRef->isLocked()) // if object is not locked, it is finished updating
{
std::cout << "Object " << objectRef->getId() << " completed. Value = " << objectRef->getValue() << std::endl;
mgr.removeObjectByIndex(i);
numCompleted++;
}
}
}
system("pause");
}
Looks like you've got a thread that is trying to join itself.
While I was trying to understand your solution I was simplifying it a lot. And I come to point that you use std::thread::join() method in a wrong way.
std::thread provide capabilities to wait for it completion (non-spin wait) -- In your example you wait for thread completion in infinite loop (snip wait) that will consume CPU time heavily.
You should call std::thread::join() from other thread to wait for thread completion. Mutex in Object in your example is not necessary. Moreover, you missed one mutex to synchronize access to std::cout, which is not thread-safe. I hope the example below will help.
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
#include <cassert>
// cout is not thread-safe
std::recursive_mutex cout_mutex;
class Object {
public:
Object(int sleepTime, unsigned int id)
: _sleepTime(sleepTime), _id(id), _value(0) {}
void runUpdate() {
if (!_thread.joinable())
_thread = std::thread(&Object::_update, this);
}
void waitForResult() {
_thread.join();
}
unsigned int getId() const { return _id; }
unsigned int getValue() const { return _value; }
private:
void _update() {
{
{
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "thread " << _id << " sleeping for " << _sleepTime << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(_sleepTime));
_value = _id * 10;
}
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "Object " << getId() << " completed. Value = " << getValue() << std::endl;
}
unsigned int _sleepTime;
unsigned int _id;
unsigned int _value;
std::thread _thread;
};
class ObjectManager : public std::vector<std::shared_ptr<Object>> {
public:
void runUpdate() {
for (auto it = this->begin(); it != this->end(); ++it)
(*it)->runUpdate();
}
void waitForAll() {
auto it = this->begin();
while (it != this->end()) {
(*it)->waitForResult();
it = this->erase(it);
}
}
};
int main(int argc, char* argv[]) {
enum {
TEST_OBJECTS_NUM = 2,
};
srand(static_cast<unsigned int>(time(nullptr)));
ObjectManager mgr;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(TEST_OBJECTS_NUM);
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
objectSleepTimes.push_back(rand() * 9 / RAND_MAX + 1); // 1..10 seconds
// Create some objects
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
mgr.push_back(std::make_shared<Object>(objectSleepTimes[i], i));
assert(mgr.size() == TEST_OBJECTS_NUM);
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (size_t i = 0; i < mgr.size(); ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.runUpdate();
mgr.waitForAll();
//system("pause"); // use Ctrl+F5 to run the app instead. That's more reliable in case of sudden app exit.
}
About is it a reasonable thing to do...
A better approach is to create an object update queue. Objects that need to be updated are added to this queue, which can be fulfilled by a group of threads instead of one thread per object.
The benefits are:
No 1-to-1 correspondence between thread and objects. Creating a thread is a heavy operation, probably more expensive than most update code for a single object.
Supports thousands of objects: with your solution you would need to create thousands of threads, which you will find exceeds your OS capacity.
Can support additional features like declaring dependencies between objects or updating a group of related objects as one operation.
I'm kind of stuck here.....
I want to transfer money from one bank account to another. There are a bunch of users and each user is a thread doing some transactions on bank accounts.
I tried different solutions but it seems that it always results in a race condition when doing transactions. The code i have is this one:
#include <mutex>
class Account
{
private:
std::string name_;
unsigned int balance_;
std::mutex classMutex_;
public:
Account(std::string name, unsigned int balance);
virtual ~Account();
void makePayment_sync(unsigned int payment);
void takeMoney_sync(unsigned int payout);
void makeTransaction_sync(unsigned int transaction, Account& toAccount);
};
unsigned int Account::getBalance_sync()
{
std::lock_guard<std::mutex> guard(classMutex_);
return balance_;
}
void Account::makePayment_sync(unsigned int payment)
{
std::lock_guard<std::mutex> guard(classMutex_);
balance_ += payment;
}
void Account::takeMoney_sync(unsigned int payout)
{
std::lock_guard<std::mutex> guard(classMutex_);
balance_ -= payout;
}
void Account::makeTransaction_sync(unsigned int transaction, Account& toAccount)
{
std::lock_guard<std::mutex> lock(classMutex_);
this->balance_ -= transaction;
toAccount.balance_ += transaction;
}
Note: I called the methods foo_sync because there should be also a case where there result should show race conditions.
But yeah I'm kind of stuck here...tried also this method, where i created a new mutex: mutex_
class Account
{
private:
std::string name_;
unsigned int balance_;
std::mutex classMutex_, mutex_;
...
void Account::makeTransaction_sync(unsigned int transaction, Account& toAccount)
{
std::unique_lock<std::mutex> lock1(this->mutex_, std::defer_lock);
std::unique_lock<std::mutex> lock2(toAccount.mutex_, std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
this->balance_ -= transaction;
toAccount.balance_ += transaction;
}
but I got some weird errors during runtime! Any suggestions/hints/ideas to solve this problem! Thanks in advance :)
OK, here's what I think is a reasonable starting point for your class.
It's not the only way to do it, but there are some principles used that I adopt in my projects. See comments inline for explanations.
This is a complete example. for clang/gcc compile and run with:
c++ -o payment -O2 -std=c++11 payment.cpp && ./payment
If you require further clarification, please feel free to ask:
#include <iostream>
#include <mutex>
#include <cassert>
#include <stdexcept>
#include <thread>
#include <vector>
#include <random>
class Account
{
using mutex_type = std::mutex;
using lock_type = std::unique_lock<mutex_type>;
std::string name_;
int balance_;
// mutable because we'll want to be able to lock a const Account in order to get a balance
mutable mutex_type classMutex_;
public:
Account(std::string name, int balance)
: name_(std::move(name))
, balance_(balance)
{}
// public interface takes a lock and then defers to internal interface
void makePayment(int payment) {
auto lock = lock_type(classMutex_);
modify(lock, payment);
}
void takeMoney(int payout) {
makePayment(-payout);
}
int balance() const {
auto my_lock = lock_type(classMutex_);
return balance_;
}
void transfer_to(Account& destination, int amount)
{
// try/catch in case one part of the transaction threw an exception.
// we don't want to lose money in such a case
try {
std::lock(classMutex_, destination.classMutex_);
auto my_lock = lock_type(classMutex_, std::adopt_lock);
auto his_lock = lock_type(destination.classMutex_, std::adopt_lock);
modify(my_lock, -amount);
try {
destination.modify(his_lock, amount);
} catch(...) {
modify(my_lock, amount);
std::throw_with_nested(std::runtime_error("failed to transfer into other account"));
}
} catch(...) {
std::throw_with_nested(std::runtime_error("failed to transfer from my account"));
}
}
// provide a universal write
template<class StreamType>
StreamType& write(StreamType& os) const {
auto my_lock = lock_type(classMutex_);
return os << name_ << " = " << balance_;
}
private:
void modify(const lock_type& lock, unsigned int amount)
{
// for internal interfaces where the mutex is expected to be locked,
// i like to pass a reference to the lock.
// then I can assert that all preconditions are met
// precondition 1 : the lock is active
assert(lock.owns_lock());
// precondition 2 : the lock is actually locking our mutex
assert(lock.mutex() == &classMutex_);
balance_ += amount;
}
};
// public overload or ostreams, loggers etc
template<class StreamType>
StreamType& operator<<(StreamType& os, const Account& a) {
return a.write(os);
}
void blip()
{
using namespace std;
static mutex m;
lock_guard<mutex> l(m);
cout << '.';
cout.flush();
}
// a test function to peturb the accounts
void thrash(Account& a, Account& b)
{
auto gen = std::default_random_engine(std::random_device()());
auto amount_dist = std::uniform_int_distribution<int>(1, 20);
auto dist = std::uniform_int_distribution<int>(0, 1);
for (int i = 0 ; i < 10000 ; ++i)
{
if ((i % 1000) == 0)
blip();
auto which = dist(gen);
auto amount = amount_dist(gen);
// make sure we transfer in both directions in order to
// cause std::lock() to resolve deadlocks
if (which == 0)
{
b.takeMoney(1);
a.transfer_to(b, amount);
a.makePayment(1);
}
else {
a.takeMoney(1);
b.transfer_to(a, amount);
b.makePayment(1);
}
}
}
auto main() -> int
{
using namespace std;
Account a("account 1", 100);
Account b("account 2", 0);
cout << "a : " << a << endl;
cout << "b : " << b << endl;
// thrash 50 threads to give it a thorough test
vector<thread> threads;
for(int i = 0 ; i < 50 ; ++i) {
threads.emplace_back(std::bind(thrash, ref(a), ref(b)));
}
for (auto& t : threads) {
if (t.joinable())
t.join();
}
cout << endl;
cout << "a : " << a << endl;
cout << "b : " << b << endl;
// check that no money was lost
assert(a.balance() + b.balance() == 100);
return 0;
}
example output:
a : account 1 = 100
b : account 2 = 0
....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
a : account 1 = 7338
b : account 2 = -7238