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;
}
Related
This has been driving me insane for hours - I'm new to C++: I can't figure out why my programs thinks I want it do this.
I have a class House
class House{
private:
int number;
std::string family;
public:
House(int n, std::string f){
this->number = n;
this->family = f;
}
House(){
this->number = 0;
this->family = "unassigned";
}
void whoLivesHere(){
std::cout<<"The"<<family<<"lives here."<<std::endl;
}
};
I have another class Neighborhood
class Neighborhood{
private:
int size;
House houses[100];
public:
Neighborhood(){
this->size=0;
}
void addHouse(House h){
this->houses[this->size] = h;
this->size++;
}
void whoLivesHere(){
for(int i=0; i<this->size; i++){
this->houses[this->size].whoLivesHere();
}
}
};
And this is what is happening on my main.
int main(){
Neighborhood n1;
House h1(1,"Johnsons");
House h2(1,"Jones");
n1.addHouse(h1);
n1.addHouse(h2);
n1.whoLivesHere();
return 0;
}
And what I get on the Terminal is this.
The unassigned lives here
The unassigned lives here
The unassigned lives here
Why didn't the new objects replace the first two default objects?
Why show three objects? If size should be 1.
Thank you tonnes in advance!
You can make short work of this problem by using the tools the C++ Standard Library gives you, like this:
#include <string>
#include <vector>
#include <iostream>
int main() {
std::vector<House> neighborhood;
// emplace_back() forwards arguments to the constructor
neighborhood.emplace_back(1, "Johnson");
neighborhood.emplace_back(2, "Jones");
// No need to track size, std::vector does that for you: size(),
// but that's not even needed to iterate, you can just do this:
for (auto& house : neighborhood) {
house.whoLivesHere();
}
return 0;
}
Here I've cleaned up your House implementation:
class House {
private:
int number;
std::string family;
public:
// Tip: Use constructor lists
House(int n, const std::string& f) : number(n), family(f) { };
// Useful even for defaults
House() : number(0), family("unassigned") { };
// Flag methods that don't modify anything as const
void whoLivesHere() const {
std::cout << "The " << family << " lives here at number " << number << "." << std::endl;
}
};
I'm creating a node system (similar to eg. UE4 or Blender's Cycles) in which i can create nodes of different types and use them later. At the moment I have 2 classes of nodes with output functions like these:
class InputInt
{
public:
int output()
{
int x;
std::cin>>x;
return x;
}
};
class RandomInt
{
public:
int rand10()
{
int x;
x = rand()%10;
return x;
}
int rand100()
{
int x;
x = rand()%100;
return x;
}
};
I don't pass anything to these nodes. Now I want to create a node which takes and output function from and object of one of above classes. Here is how I implemented it to use InputInt node only:
class MultiplyBy2
{
typedef int (InputInt::*func)();
func input_func;
InputInt *obj;
public:
MultiplyBy2(InputInt *object, func i): obj(object), input_func(i) {}
int output()
{
return (obj->*input_func)()*2;
}
};
Having this I can create and use object of MultiplyBy2 in main() and it works perfectly.
int main()
{
InputInt input;
MultiplyBy2 multi(&input, input.output);
std::cout<<multi.output()<<std::endl;
}
It doesn't obviously work for object of RandomInt as I have to pass *InputInt object to MultiplyBy2 object. Is there a way to make MultiplyBy2 take any kind of an object with its output function eg. like this?
int main()
{
RandomInt random;
MultiplyBy2 multi2(&random, random.rand10);
std::cout<<multi2.output()<<std::endl;
}
An alternative approach, using a common base class with virtual methods:
#include <iostream>
struct IntOp {
virtual int get() = 0;
};
struct ConstInt: IntOp {
int n;
explicit ConstInt(int n): n(n) { }
virtual int get() override { return n; }
};
struct MultiplyIntInt: IntOp {
IntOp *pArg1, *pArg2;
MultiplyIntInt(IntOp *pArg1, IntOp *pArg2): pArg1(pArg1), pArg2(pArg2) { }
virtual int get() override { return pArg1->get() * pArg2->get(); }
};
int main()
{
ConstInt i3(3), i4(4);
MultiplyIntInt i3muli4(&i3, &i4);
std::cout << i3.get() << " * " << i4.get() << " = " << i3muli4.get() << '\n';
return 0;
}
Output:
3 * 4 = 12
Live Demo on coliru
As I mentioned std::function in post-answer conversation with OP, I fiddled a bit with this idea and got this:
#include <iostream>
#include <functional>
struct MultiplyIntInt {
std::function<int()> op1, op2;
MultiplyIntInt(std::function<int()> op1, std::function<int()> op2): op1(op1), op2(op2) { }
int get() { return op1() * op2(); }
};
int main()
{
auto const3 = []() -> int { return 3; };
auto const4 = []() -> int { return 4; };
auto rand100 = []() -> int { return rand() % 100; };
MultiplyIntInt i3muli4(const3, const4);
MultiplyIntInt i3muli4mulRnd(
[&]() -> int { return i3muli4.get(); }, rand100);
for (int i = 1; i <= 10; ++i) {
std::cout << i << ".: 3 * 4 * rand() = "
<< i3muli4mulRnd.get() << '\n';
}
return 0;
}
Output:
1.: 3 * 4 * rand() = 996
2.: 3 * 4 * rand() = 1032
3.: 3 * 4 * rand() = 924
4.: 3 * 4 * rand() = 180
5.: 3 * 4 * rand() = 1116
6.: 3 * 4 * rand() = 420
7.: 3 * 4 * rand() = 1032
8.: 3 * 4 * rand() = 1104
9.: 3 * 4 * rand() = 588
10.: 3 * 4 * rand() = 252
Live Demo on coliru
With std::function<>, class methods, free-standing functions, and even lambdas can be used in combination. So, there is no base class anymore needed for nodes. Actually, even nodes are not anymore needed (explicitly) (if a free-standing function or lambda is not counted as "node").
I must admit that graphical dataflow programming was subject of my final work in University (though this is a long time ago). I remembered that I distinguished
demand-driven execution vs.
data-driven execution.
Both examples above are demand-driven execution. (The result is requested and "pulls" the arguments.)
So, my last sample is dedicated to show a simplified data-driven execution (in principle):
#include <iostream>
#include <vector>
#include <functional>
struct ConstInt {
int n;
std::vector<std::function<void(int)>> out;
ConstInt(int n): n(n) { eval(); }
void link(std::function<void(int)> in)
{
out.push_back(in); eval();
}
void eval()
{
for (std::function<void(int)> &f : out) f(n);
}
};
struct MultiplyIntInt {
int n1, n2; bool received1, received2;
std::vector<std::function<void(int)>> out;
void set1(int n) { n1 = n; received1 = true; eval(); }
void set2(int n) { n2 = n; received2 = true; eval(); }
void link(std::function<void(int)> in)
{
out.push_back(in); eval();
}
void eval()
{
if (received1 && received2) {
int prod = n1 * n2;
for (std::function<void(int)> &f : out) f(prod);
}
}
};
struct Print {
const char *text;
explicit Print(const char *text): text(text) { }
void set(int n)
{
std::cout << text << n << '\n';
}
};
int main()
{
// setup data flow
Print print("Result: ");
MultiplyIntInt mul;
ConstInt const3(3), const4(4);
// link nodes
const3.link([&mul](int n) { mul.set1(n); });
const4.link([&mul](int n) { mul.set2(n); });
mul.link([&print](int n) { print.set(n); });
// done
return 0;
}
With the dataflow graph image (provided by koman900 – the OP) in mind, the out vectors represent outputs of nodes, where the methods set()/set1()/set2() represent inputs.
Output:
Result: 12
Live Demo on coliru
After connection of graph, the source nodes (const3 and const4) may push new results to their output which may or may not cause following operations to recompute.
For a graphical presentation, the operator classes should provide additionally some kind of infrastructure (e.g. to retrieve a name/type and the available inputs and outputs, and, may be, signals for notification about state changes).
Surely, it is possible to combine both approaches (data-driven and demand-driven execution). (A node in the middle may change its state and requests new input to push new output afterwards.)
You can use templates.
template <typename UnderlyingClass>
class MultiplyBy2
{
typedef int (UnderlyingClass::*func)();
func input_func;
UnderlyingClass *obj;
public:
MultiplyBy2(UnderlyingClass *object, func i) : obj(object), input_func(i) {}
int output()
{
return (obj->*input_func)() * 2;
}
};
int main()
{
// test
InputInt ii;
MultiplyBy2<InputInt> mii{ &ii, &InputInt::output };
RandomInt ri;
MultiplyBy2<RandomInt> mri{ &ri, &RandomInt::rand10 };
}
This is a bit convoluted. However I think you should be making an interface or class that returns a value and the objects should inherit from this. Then the operator class can take any class that inherits from the base/interface. Eg Make an BaseInt class that stores an int and has the output method/ RandomInt and InputInt should inherit from BaseInt
How to run a function of an actual object, which is stored in the container using OOP?
Background: I'm writing a game. There is a set of 4 interconnected rooms. There are two different room types and two different player types. Players should run as threads. The Killer should have a fight with a normal player in the Action room. In the second type of room, nothing should happen. The game logic and code is simplified.
When the thread starts, void Player::operator()() is being executed. The player enters the room, does his action initializeAction(), and leaves it. In case of a Killer, his initializeAction() leads to room->actionInRoom(*this), which executes player.inActionRoom().
The problem is in this code void Killer::inActionRoom():
std::vector<Player> &playersWithoutKillers = room->getPlayersWithoutKillers();
auto it = playersWithoutKillers.begin();
std::advance(it, 0);
Player chosenPlayerForFight = *it;
...
chosenPlayerForFight.decreasePoints();
where chosenPlayerForFight.decreasePoints(); does not decrease the points for the actual player, but I think it does it for a copy of an object.
If I run the code, this mistake is visible: OtherPlayer's points will always reset to 1. If I'm decreasing it every time the fight occurs, the negative value is expected.
-> Killer in Forth room Killer 11 vs OtherPlayer 1
Starting decreasing points from 1
Ending decreasing points from 0
I tried to fix the code, mainly by making sure the reference of an object is passed.
Main.cpp:
#include <iostream>
#include <memory>
#include <thread>
#include "Room.h"
#include "Player.h"
std::mutex globalMessageMutex;
int main() {
auto first = std::make_shared<RelaxRoom>("First room");
auto second = std::make_shared<ActionRoom>("Second room");
auto third = std::make_shared<RelaxRoom>("Third room");
auto forth = std::make_shared<ActionRoom>("Forth room");
first->setRoomPair(second, forth);
second->setRoomPair(third, first);
third->setRoomPair(forth, second);
forth->setRoomPair(first, third);
std::vector<std::thread> players;
players.emplace_back(OtherPlayer("OtherPlayer", first));
players.emplace_back(Killer("Killer", first));
for (auto &t : players) {
if (t.joinable()) {
t.join();
}
}
return 0;
}
Player.h
#ifndef HW03_PLAYER_H
#define HW03_PLAYER_H
#include <string>
#include <memory>
class Room;
class Player {
friend class Room;
public:
Player(const std::string &playerName, std::shared_ptr<Room> initialTargetRoom);
void operator()();
friend bool operator== ( const Player &lhs, const Player &rhs );
const std::string &getName() const;
virtual void inActionRoom() {};
virtual void inRelaxRoom(Room &pRoom) {};
virtual bool isKiller()const;
virtual bool isOtherPlayer();
int getPoints()const;
void increasePoints();
void decreasePoints();
int points;
protected:
std::shared_ptr<Room> room;
virtual void initializeAction();
private:
std::string name;
std::shared_ptr<Room> initialRoom;
};
class OtherPlayer : public Player {
public:
OtherPlayer(const std::string &playerName, const std::shared_ptr<Room> &initialTargetRoom);
void initializeAction() override;
void inActionRoom() override;
void inRelaxRoom(Room &pRoom) override;
bool isOtherPlayer() override;
};
class Killer : public Player {
public:
Killer(const std::string &playerName, const std::shared_ptr<Room> &initialTargetRoom);
void initializeAction() override;
void inActionRoom() override;
void inRelaxRoom(Room &pRoom) override;
bool isKiller() const override;
};
#endif //HW03_PLAYER_H
Player.cpp
#include <iostream>
#include <chrono>
#include <thread>
#include <random>
#include "Player.h"
#include "Room.h"
extern std::mutex globalMessageMutex;
Player::Player(const std::string &playerName, std::shared_ptr<Room> initialTargetRoom) {
name = playerName;
initialRoom = initialTargetRoom;
room = initialTargetRoom;
points = 1;
}
void Player::operator()() {
room->enter(*this);
initializeAction();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
room->leave(*this);
while (auto nextRoom = room->getNext()) {
room = nextRoom;
nextRoom->enter(*this);
initializeAction();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
nextRoom->leave(*this);
}
}
void Player::initializeAction() {}
bool operator==(const Player &lhs, const Player &rhs) {
return lhs.name == rhs.name;
}
const std::string &Player::getName() const {
return name;
}
bool Player::isKiller() const {
return false;
}
bool Player::isOtherPlayer() {
return false;
}
int Player::getPoints() const {
return points;
}
void Player::increasePoints() {
points++;
}
void Player::decreasePoints() {
std::cout << "Starting decreasing points from " << points << std::endl;
points--;
std::cout << "Ending decreasing points from " << points << std::endl;
}
OtherPlayer::OtherPlayer(const std::string &playerName, const std::shared_ptr<Room> &initialTargetRoom) : Player(playerName,
initialTargetRoom) {}
void OtherPlayer::initializeAction() {
room->actionInRoom(*this);
}
void OtherPlayer::inActionRoom() {}
void OtherPlayer::inRelaxRoom(Room &pRoom) {}
bool OtherPlayer::isOtherPlayer() {
return true;
}
Killer::Killer(const std::string &playerName, const std::shared_ptr<Room> &initialTargetRoom) : Player(playerName,
initialTargetRoom) {}
void Killer::initializeAction() {
room->actionInRoom(*this);
}
void Killer::inActionRoom() {
std::lock_guard<std::mutex> ml(globalMessageMutex);
if (!room->getPlayersWithoutKillers().empty()) {
**std::vector<Player> &playersWithoutKillers = room->getPlayersWithoutKillers();**
**auto it = playersWithoutKillers.begin();
std::advance(it, 0);
Player chosenPlayerForFight = *it;**
auto killersVitality = this->getPoints();
auto othersPlayerPoints = chosenPlayerForFight.getPoints();
std::cout << "-> Killer in " << room->getName() << " " << this->getName() << " " << killersVitality
<< " vs " << chosenPlayerForFight.getName() << " " << othersPlayerPoints << std::endl;
this->increasePoints();
**chosenPlayerForFight.decreasePoints();**
}
}
void Killer::inRelaxRoom(Room &pRoom) {
}
bool Killer::isKiller() const {
return true;
}
Room.h
#ifndef HW03_ROOM_H
#define HW03_ROOM_H
#include <string>
#include <vector>
#include <condition_variable>
class Player;
class Room {
public:
Room(const std::string &roomName);
void setRoomPair(std::shared_ptr<Room> firstRoom, std::shared_ptr<Room> secondRoom);
std::shared_ptr<Room> getNext();
void enter(Player &player);
void leave(Player &player);
virtual void actionInRoom(Player &player)= 0;
const std::string &getName() const;
const std::vector<Player> &getPlayers();
std::vector<Player> &getPlayersWithoutKillers();
protected:
std::string name;
size_t killersCount;
size_t playersWithoutKillersCount;
private:
std::vector<Player> players;
std::vector<Player> playersWithoutKillers;
std::condition_variable cv;
std::mutex mutex;
std::pair<std::shared_ptr<Room>, std::shared_ptr<Room>> roomPair;
void updateCounterPlayerLeaves(Player &player);
void updateCounterPlayerEnters(Player &player);
};
class ActionRoom : public Room {
public:
ActionRoom(const std::string &roomName) : Room(roomName) {}
void actionInRoom(Player &player) override;
};
class RelaxRoom : public Room {
public:
RelaxRoom(const std::string &roomName) : Room(roomName) {}
void actionInRoom(Player &player) override;
};
#endif //HW03_ROOM_H
Room.cpp
#include <iostream>
#include <algorithm>
#include <random>
#include "Room.h"
#include "Player.h"
#include <mutex>
extern std::mutex globalMessageMutex;
Room::Room(const std::string &roomName) {
name = roomName;
}
const std::string &Room::getName() const {
return name;
}
std::shared_ptr<Room> Room::getNext() {
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::mt19937 engine(seed);
std::uniform_int_distribution<int> randomGenerator(0, 1);
auto randomNumber = randomGenerator(engine);
if (randomNumber) {
return roomPair.second;
}
return roomPair.first;
}
void Room::enter(Player &player) {
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [this, &player] {
return true;
}
);
players.push_back(player);
updateCounterPlayerEnters(player);
std::lock_guard<std::mutex> ml(globalMessageMutex);
std::cout << name << ": killers: " << killersCount << ", other players: " << playersWithoutKillersCount <<
std::endl;
}
void Room::updateCounterPlayerEnters(Player &player) {
if (player.isKiller()) {
killersCount++;
} else {
playersWithoutKillersCount++;
playersWithoutKillers.push_back(player);
}
}
void Room::leave(Player &player) {
{
std::lock_guard<std::mutex> lock(mutex);
auto it = std::find(players.begin(), players.end(), player);
if (it == players.end()) {
return;
}
players.erase(it);
updateCounterPlayerLeaves(player);
}
cv.notify_all();
}
void Room::updateCounterPlayerLeaves(Player &player) {
if (player.isKiller()) {
killersCount--;
} else {
playersWithoutKillersCount--;
auto it = std::find(playersWithoutKillers.begin(), playersWithoutKillers.end(), player);
if (it == playersWithoutKillers.end()) {
return;
}
playersWithoutKillers.erase(it);
}
}
void Room::setRoomPair(std::shared_ptr<Room> firstRoom, std::shared_ptr<Room> secondRoom) {
roomPair.first = std::move(firstRoom);
roomPair.second = std::move(secondRoom);
}
const std::vector<Player> &Room::getPlayers() {
return players;
}
std::vector<Player> &Room::getPlayersWithoutKillers() {
return playersWithoutKillers;
}
void ActionRoom::actionInRoom(Player &player) {
player.inActionRoom();
}
void RelaxRoom::actionInRoom(Player &player) {
player.inRelaxRoom(*this);
}
Not a complete answer, since you said this is a homework assignment.
However, one pattern you can use to solve this problem is to have your container store std::unique_ptr<some_base_class> values, or if necessary std::shared_ptr<some_base_class>, and fill them with pointers to derived objects. For example: container.emplace_back(static_cast<some_base_class*>(new derived_class(foo, bar, baz))); will construct everything in place inside your container, so the compiler doesn’t need to make any temporary copies. You might also write something like:
std::vector<std::unique_ptr<some_base_class>> container;
{
std::unique_ptr<derived_class> temp =
make_unique<derived_class>();
temp->setup( foo, bar, baz );
container.emplace_back(static_cast<some_base_class*>(temp.release()));
// temp is now empty, and the pointer in the container now owns the object.
}
You can use the full interface of some_base_class through these smart pointers, with ->, but if you need to turn them back into references to derived objects, you would use RTTI and dynamic_cast.
There are other approaches, including a discriminated union and std::variant, but storing smart pointers to the base class that defines your interface is what I recommend you try here.
I'd appreciate it if someone suggests a way to finally delete context objects used represent a job processed through a pipeline of steps.
Here in the following code an object of class text_file_processing_request is created and sent to the io_service. My pipeline here is made up of one step, there could be more steps in real code.
Now, I would like to get opinions on the best way to delete these objects of type text_file_processing_request once they are done with.
Thank you!
#include <iostream>
#include "boost/asio.hpp"
#include "boost/thread.hpp"
using namespace std;
namespace asio = boost::asio;
typedef std::unique_ptr<asio::io_service::work> work_ptr;
typedef boost::function<void(void) > parse_and_aggregate_fun_t;
class file_processing_request{
public:
virtual void process(int num) = 0;
};
class text_file_processing_request : public file_processing_request {
public:
virtual void process(int num) {
cout << "text_file_processing_request::process " << num << endl;
}
};
class processor {
public:
processor(int threads) : thread_count(threads) {
service = new asio::io_service();
work = new work_ptr(new asio::io_service::work(*(service)));
for (int i = 0; i < this->thread_count; ++i)
workers.create_thread(boost::bind(&asio::io_service::run, service));
}
void post_task(parse_and_aggregate_fun_t job){
this->service->post(job);
}
void stop(){
this->work->reset();
}
void wait(){
this->workers.join_all();
}
private:
int thread_count;
work_ptr * work;
asio::io_service* service;
boost::thread_group workers;
};
class job_discoverer {
public:
job_discoverer(processor *p): worker(p){}
void start_producing(){
do {
file_processing_request * cPtr = new text_file_processing_request();
this->worker->post_task(boost::bind(&file_processing_request::process, cPtr, 42));
} while (0);
this->worker->stop(); // no more data to process
}
private:
processor *worker;
};
int main(int argc, char** argv) {
processor *pr = new processor(4);
job_discoverer disocverer(pr);
disocverer.start_producing();
pr->wait();
delete pr;
return 0;
}
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.