The purpose of this document is to capture research and reference material I generate while I work toward the project objectives, as well as document the coding itself.
Reimplement c++11 lock_guard (NOT use std::lock_guard). Use namespace to allow you to call your class impl "lock_guard" and not conflict with std::lock_guard
LockGuard.h // header file only impl
Having difficulty parsing prompt. I took "LockGuard.h // header file only impl" in the prompt to indicate that I am to build a declaration for the class based off of a .h file that was provided.
I see that no LockGuard.h was attached to the email. Perhaps I misunderstand what I am intended to do?
Upon asking for clarification, was provided this:
You need to create LockGuard.h. I did not attach it. You need to re-create the functionality of std::lock_guard. You can put all the code in a header file. It’s a common technique for STL and other utilities and is often referred to as “header only” so the user of the utility knows they can just include the header and not have to link a library to their project.
You will need to fully implement the functions. In the header file.
LockGuard.h
Set namespace to avoid naming collisions with std::lock_guard. std:: being a name space.
class LockGuard {
… implementation …
}
Need a better understanding of what lock_guard actually does. Brief googling leads to a template for calling the std class here: https://en.cppreference.com/w/cpp/thread/lock_guard Suspect I will want to call my class in a similar fashion. Pasting code here for reference:
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
// g_i_mutex is automatically released when lock
// goes out of scope
}
int main()
{
std::cout << "main: " << g_i << '\n';
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
std::cout << "main: " << g_i << '\n';
}
Now, to look at the headers. https://en.cppreference.com/w/cpp/header/mutex has these:
class mutex {
public:
constexpr mutex() noexcept;
~mutex();
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
void lock();
bool try_lock();
void unlock();
typedef /*implementation-defined*/ native_handle_type;
native_handle_type native_handle();
};
and
template <class Mutex>
class lock_guard {
public:
typedef Mutex mutex_type;
explicit lock_guard(mutex_type& m);
lock_guard(mutex_type& m, adopt_lock_t);
~lock_guard();
lock_guard(lock_guard const&) = delete;
lock_guard& operator=(lock_guard const&) = delete;
private:
mutex_type& pm; // exposition only
};
I had very little luck finding fully implemented functions on the web, so I spun up an Ubuntu VM and searched around. I found it in /usr/include/c++/5
under Ubuntu Desktop 17.10:
/** @brief A movable scoped lock type.
*
* A unique_lock controls mutex ownership within a scope. Ownership of the
* mutex can be delayed until after construction and can be transferred
* to another unique_lock by move construction or move assignment. If a
* mutex lock is owned when the destructor runs ownership will be released.
*/
template<typename _Mutex>
class lock_guard
{
public:
typedef _Mutex mutex_type;
explicit lock_guard(mutex_type& __m) : _M_device(__m)
{ _M_device.lock(); }
lock_guard(mutex_type& __m, adopt_lock_t) : _M_device(__m)
{ } // calling thread owns mutex
~lock_guard()
{ _M_device.unlock(); }
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
mutex_type& _M_device;
};
I found a template for calling the std class here: http://www.cplusplus.com/reference/mutex/lock_guard/ which I will use for initial testing for this step:
// lock_guard example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::lock_guard
#include <stdexcept> // std::logic_error
std::mutex mtx;
void print_even (int x) {
if (x%2==0) std::cout << x << " is even\n";
else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
try {
// using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
std::lock_guard<std::mutex> lck (mtx);
print_even(id);
}
catch (std::logic_error&) {
std::cout << "[exception caught]\n";
}
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads) th.join();
return 0;
}
I copied the std::lock_guard class, added an std:: namespace reference to the reference that needed it, and encapsulated the new class in a new namespace "chal" for challenge:
namespace chal { // challenge namespace
/** @brief A movable scoped lock type.
*
* This class has been kept as identical to std::lock_guard as possible
*
* A unique_lock controls mutex ownership within a scope. Ownership of the
* mutex can be delayed until after construction and can be transferred
* to another unique_lock by move construction or move assignment. If a
* mutex lock is owned when the destructor runs ownership will be released.
*/
template<typename _Mutex> // returns type determined by calling function
class LockGuard
{
public:
typedef _Mutex mutex_type;
explicit LockGuard(mutex_type& __m) : _M_device(__m) // no implicit constructor
{ _M_device.lock(); }
LockGuard(mutex_type& __m, std::adopt_lock_t) : _M_device(__m)
{ } // calling thread owns mutex
~LockGuard()
{ _M_device.unlock(); }
// generate compile error if copy attempted
// (supposed to be un-copyable)
LockGuard(const LockGuard&) = delete; // copy constructor
LockGuard& operator=(const LockGuard&) = delete; // copy assignment operator
private:
mutex_type& _M_device;
};
}
For the testbench, I copied the generic example code and changed the reference for std::lock_guard to chal::LockGuard .
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::adopt_lock
#include "LockGuard.h" // chal::LockGuard
std::mutex mtx; // mutex for critical section
void print_thread_id (int id) {
mtx.lock();
// Swap next two lines to test chal::LockGuard against std::lock_guard
//std::lock_guard<std::mutex> lck (mtx, std::adopt_lock);
chal::LockGuard<std::mutex> lck (mtx, std::adopt_lock);
std::cout << "thread #" << id << '\n';
}
int main ()
{
std::thread threads[10]; // creates an array of 10 thread objects
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
// for each element th in array threads, join th
// use auto& to guarantee sharing variable by reference
for (auto& th : threads) th.join();
return 0;
}
Area to capture information for reference
lock_guard is a function that implements a programming idiom called Resource Acquisition is Initialization or RAII. In this idiom, resource acquisition is tied to object lifetime. lock_guard ties a resource (mutex) to an object (the calling function). The mutex is released upon execution leaving the scope of the function that called lock_guard. This provides a safeguard against an exception in the calling function preventing it from releasing the lock directly.
https://www.youtube.com/watch?v=ojOUIg13g3I
template <class Mutex>
The template directive is 'meta programming'; it programs what the compiler does at compile time, rather than what program does at runtime. I find the template <class identifier> function_declaration;
somewhat confusing, and prefer to substitute template <typename identifier> function_declaration;
which is synonymous.
http://www.cplusplus.com/doc/oldtutorial/templates/
Points of note:
Once a parent thread calls a child thread, it is important to encapsulate any further work the parent thread does before re-joining the child thread in a try, catch block to prevent an exception in the parent from destroying the child before it is done. Ex:
int main(){ std::thread t1(function_1); // t1 starts running try { do_something_dangerous(); } catch (...) { t1.join(); throw; ti.join(); return 0; }
https://www.youtube.com/watch?v=I-hZkUa9mIs <<< Interesting that this video says calling the mutex's own lock&unlock function is not recommended. This seems to imply that in general, it is best to use a lock_guard function. Also notes that lock_guard does not release the lock until out of scope, unlike unique_lock, which releases the lock upon locker.unlock();
. This provides a constraint on my code later.
https://www.youtube.com/watch?v=LL8wkskDlbs <<< Initial video of C++ Threading series. It may be useful to watch them all to brush up.
typedef Mutex mutex_type;
The typedef-names are aliases for existing types, and are not declarations of new types. Eg.
// simple typedef typedef unsigned long ulong;
lock_guard& operator=(lock_guard const&) = delete;
Appears to be operator overloading the equals sign. Need to parse this better.
"Passing by const reference to avoid copying". Any function that doesn't modify the class should be const.
Vector2 Add(const Vector2& other) const { return Vector2(x + other.x, y + other.y); }
The operator overload analog:
Vector2 operator+(const Vector2& other) const { return Vector2(x + other.x, y + other.y); }
An alternative approach is:
Vector2 operator+(const Vector2& other) const { return Vector2(x + other.x, y + other.y); } Vector2 Add(const Vector2& other) const { return *this + other; // deref because 'this' is const pntr in current scope }
Yet another alternative approach is:
Vector2 operator+(const Vector2& other) const { return Vector2(x + other.x, y + other.y); } Vector2 Add(const Vector2& other) const { return operator+(other); // address operator+ like a function }
Overloading the << operator to include Vector2 containing two floats
std::ostream& operator<<(std::ostream& stream, const Vector2& other) { stream << other.x << ", " << other.y; return stream; }
https://www.youtube.com/watch?v=PgGhEovFhd0
https://www.youtube.com/watch?v=mS9755gF66w <<< Says best practice: only use when usage is intuitive. Avoid if ppl would need to go to your function to understand what it does.
// for each element th in array threads, join th
// use auto& to guarantee sharing variable by reference
for (auto& th : threads) th.join();
syntax:
for ( element : collection ) {
//do something
}
https://stackoverflow.com/questions/29859796/c-auto-vs-auto
https://www.youtube.com/watch?v=2yR8dcKT-Ck
What does this do? Says it is a constructor definition. Occurs inside of a struct.
Vector2(float x, float y) : x(x), y(y) {}
https://www.youtube.com/watch?v=mS9755gF66w
Not necessary to resolve immediately. Keeping question for reference.