- Introduction
- Multitasking: A Critical Section
- Class-Level Locks
- Lock Strategy
- Processes Versus Threads
Class-Level Locks
The example in Listing 1 is very simplistic in nature. In a more realistic scenario, locking semantics are provided at the class level; that is, a mutex is included as a field in a class definition, along with an associated accessor method. Listing 4 illustrates a class called Person.
Listing 4A class-level mutex.
class Person { private: std::mutex classMutex; string name; int age; public: Person(string name, int age); virtual ~Person(); string getName(); void setName(string newName); int getAge(); void setAge(int newAge); };
Notice in Listing 4 that the class Person includes a mutex field called classMutex. As in the previous example, the mutex in Listing 4 is used to protect against simultaneous access to shared objects of class Person. Obviously, if only one thread can modify an instance of class Person, there's no need for the mutex. But if multiple threads of execution can access the same object, we need a locking mechanism, as illustrated in Listing 5.
Listing 5Implementing a class-level mutex on setters.
Person::Person(string name, int age) { this->name = name; this->age = age; } Person::~Person() { } string Person::getName() { return this->name; } void Person::setName(string newName) { std::lock_guard<std::mutex> lk(classMutex); this->name = newName; } int Person::getAge() { return this->age; } void Person::setAge(int newAge) { std::lock_guard<std::mutex> lk(classMutex); this->age = newAge; }
In Listing 5, notice that I use the lock for all data modifications. There is still a potential problem in Listing 5, though, because any access to the getter functions might potentially result in race conditions. To be rigorous, we really should use the lock for the getters as well as the setters, as in Listing 6.
Listing 6Implementing a class-level mutex on getters and setters.
Person::Person(string name, int age) { this->name = name; this->age = age; } Person::~Person() { } string Person::getName() { std::lock_guard<std::mutex> lk(classMutex); return this->name; } void Person::setName(string newName) { std::lock_guard<std::mutex> lk(classMutex); this->name = newName; } int Person::getAge() { std::lock_guard<std::mutex> lk(classMutex); return this->age; } void Person::setAge(int newAge) { std::lock_guard<std::mutex> lk(classMutex); this->age = newAge; }
In Listing 6, we should now have no race conditions. Listing 7 illustrates a complete run of the new program.
Listing 7A new program run.
Hello World - sharedDataItem: 100 Starting threads Thread ID 1 3075812208 Thread ID 2 3067419504 Thread ID 3 3059026800 Hello Concurrent World 2 - attempting to lock mutex! Hello Concurrent World 2 - successfully locked mutex! Hello Concurrent World 3 - attempting to lock mutex! Hello Concurrent World 1 - attempting to lock mutex! Hello Concurrent World 2 - just about to unlock mutex! Hello Concurrent World 3 - successfully locked mutex! Hello Concurrent World 3 - just about to unlock mutex! Hello Concurrent World 1 - successfully locked mutex! Hello Concurrent World 1 - just about to unlock mutex! After threads - sharedDataItem: 106 Time to create somebody! Person1 name: Johnny Doe Person1 age: 100 Person1 modified age: 101