简体   繁体   中英

Accessing and working with std::vector from multiple threads

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM