Commit 8cdb68e8 authored by Davis King's avatar Davis King

Added the read_write_mutex object and updated any relevant supporting objects.

--HG--
extra : convert_revision : svn%3Afdd8eb12-d10e-0410-9acb-85c331704f74/trunk%403347
parent f7dbfaba
...@@ -61,6 +61,7 @@ set (tests ...@@ -61,6 +61,7 @@ set (tests
pixel.cpp pixel.cpp
queue.cpp queue.cpp
rand.cpp rand.cpp
read_write_mutex.cpp
reference_counter.cpp reference_counter.cpp
sequence.cpp sequence.cpp
serialize.cpp serialize.cpp
......
...@@ -71,6 +71,7 @@ SRC += pipe.cpp ...@@ -71,6 +71,7 @@ SRC += pipe.cpp
SRC += pixel.cpp SRC += pixel.cpp
SRC += queue.cpp SRC += queue.cpp
SRC += rand.cpp SRC += rand.cpp
SRC += read_write_mutex.cpp
SRC += reference_counter.cpp SRC += reference_counter.cpp
SRC += sequence.cpp SRC += sequence.cpp
SRC += serialize.cpp SRC += serialize.cpp
......
// Copyright (C) 2010 Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License See LICENSE.txt for the full license.
#include <sstream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <dlib/misc_api.h>
#include <dlib/threads.h>
#include "tester.h"
namespace
{
using namespace test;
using namespace dlib;
using namespace std;
logger dlog("test.read_write_mutex");
class read_write_mutex_tester : public tester, multithreaded_object
{
public:
read_write_mutex_tester (
) :
tester ("test_read_write_mutex",
"Runs tests on the read_write_mutex component.")
{
register_thread(*this, &read_write_mutex_tester::thread_write);
register_thread(*this, &read_write_mutex_tester::thread_write);
register_thread(*this, &read_write_mutex_tester::thread_write);
register_thread(*this, &read_write_mutex_tester::thread_readonly);
register_thread(*this, &read_write_mutex_tester::thread_readonly);
register_thread(*this, &read_write_mutex_tester::thread_readonly);
register_thread(*this, &read_write_mutex_tester::thread_readonly2);
register_thread(*this, &read_write_mutex_tester::thread_readonly2);
register_thread(*this, &read_write_mutex_tester::thread_readonly2);
}
read_write_mutex m;
mutex mut;
int num_write;
int num_read;
int max_read;
bool failure;
void thread_write ()
{
// do this so that the readonly threads can get into their loops first. This way
// we can see if the mutex lets many readers into their area
dlib::sleep(250);
for (int i = 0; i < 6; ++i)
{
auto_mutex lock(m);
mut.lock();
++num_write;
mut.unlock();
// only one write thread should ever be active at once
if (num_write != 1)
{
failure = true;
dlog << LERROR << "1";
}
dlib::sleep(300);
// only one write thread should ever be active at once
if (num_write != 1)
{
failure = true;
dlog << LERROR << "2";
}
mut.lock();
--num_write;
mut.unlock();
print_spinner();
}
dlog << LINFO << "exit thread_write()";
}
void do_readonly_stuff()
{
mut.lock();
++num_read;
mut.unlock();
max_read = max(num_read, max_read);
if (num_write != 0)
{
failure = true;
dlog << LERROR << "3";
}
dlib::sleep(300);
max_read = max(num_read, max_read);
if (num_write != 0)
{
failure = true;
dlog << LERROR << "4";
}
mut.lock();
--num_read;
mut.unlock();
print_spinner();
}
void thread_readonly ()
{
for (int i = 0; i < 6; ++i)
{
auto_mutex_readonly lock(m);
do_readonly_stuff();
}
dlog << LINFO << "exit thread_readonly()";
}
void thread_readonly2 ()
{
for (int i = 0; i < 6; ++i)
{
m.lock_readonly();
auto_unlock_readonly unlock(m);
do_readonly_stuff();
}
dlog << LINFO << "exit thread_readonly2()";
}
void perform_test (
)
{
num_write = 0;
num_read = 0;
max_read = 0;
failure = false;
// doing this big block of weird stuff should have no effect.
{
m.unlock();
m.lock_readonly();
m.lock_readonly();
m.unlock();
m.unlock_readonly();
m.unlock();
m.unlock_readonly();
m.unlock();
m.unlock_readonly();
m.lock();
m.unlock_readonly();
m.unlock_readonly();
m.unlock();
}
// start up our testing threads
start();
// wait for the threads to finish
wait();
DLIB_TEST(failure == false);
DLIB_TEST_MSG(max_read == 6, "max_read: "<< max_read);
}
} a;
}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "threads/thread_specific_data_extension.h" #include "threads/thread_specific_data_extension.h"
#include "threads/thread_function_extension.h" #include "threads/thread_function_extension.h"
#include "threads/thread_pool_extension.h" #include "threads/thread_pool_extension.h"
#include "threads/read_write_mutex_extension.h"
#endif // DLIB_THREADs_ #endif // DLIB_THREADs_
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "threads_kernel.h" #include "threads_kernel.h"
#include "rmutex_extension.h" #include "rmutex_extension.h"
#include "read_write_mutex_extension.h"
#include "auto_mutex_extension_abstract.h" #include "auto_mutex_extension_abstract.h"
namespace dlib namespace dlib
...@@ -20,52 +21,98 @@ namespace dlib ...@@ -20,52 +21,98 @@ namespace dlib
- the mutex pointed to by m is locked - the mutex pointed to by m is locked
- if (r != 0) then - if (r != 0) then
- the mutex pointed to by r is locked - the mutex pointed to by r is locked
- exactly one of r or m is not 0. - if (rw != 0) then
- the mutex pointed to by rw is locked
- exactly one of r, m, or rw is not 0.
CONVENTION CONVENTION
- if (m != 0) then - if (m != 0) then
- the mutex pointed to by m is locked - the mutex pointed to by m is locked
- if (r != 0) then - if (r != 0) then
- the mutex pointed to by r is locked - the mutex pointed to by r is locked
- exactly one of r or m is not 0. - if (rw != 0) then
- the mutex pointed to by rw is locked
- exactly one of r, m, or rw is not 0.
!*/ !*/
public: public:
auto_mutex ( explicit auto_mutex (
const mutex& m_ const mutex& m_
) : m(&m_), ) : m(&m_),
r(0) r(0),
rw(0)
{ {
m->lock(); m->lock();
} }
auto_mutex ( explicit auto_mutex (
const rmutex& r_ const rmutex& r_
) : m(0), ) : m(0),
r(&r_) r(&r_),
rw(0)
{ {
r->lock(); r->lock();
} }
explicit auto_mutex (
const read_write_mutex& rw_
) : m(0),
r(0),
rw(&rw_)
{
rw->lock();
}
~auto_mutex ( ~auto_mutex (
) )
{ {
if (m != 0) if (m != 0)
m->unlock(); m->unlock();
else else if (r != 0)
r->unlock(); r->unlock();
else
rw->unlock();
} }
private: private:
const mutex* m; const mutex* m;
const rmutex* r; const rmutex* r;
const read_write_mutex* rw;
// restricted functions // restricted functions
auto_mutex(auto_mutex&); // copy constructor auto_mutex(auto_mutex&); // copy constructor
auto_mutex& operator=(auto_mutex&); // assignment operator auto_mutex& operator=(auto_mutex&); // assignment operator
}; };
// ----------------------------------------------------------------------------------------
class auto_mutex_readonly
{
public:
explicit auto_mutex_readonly (
const read_write_mutex& rw_
) : rw(rw_)
{
rw.lock_readonly();
}
~auto_mutex_readonly (
)
{
rw.unlock_readonly();
}
private:
const read_write_mutex& rw;
// restricted functions
auto_mutex_readonly(auto_mutex_readonly&); // copy constructor
auto_mutex_readonly& operator=(auto_mutex_readonly&); // assignment operator
};
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "threads_kernel_abstract.h" #include "threads_kernel_abstract.h"
#include "rmutex_extension_abstract.h" #include "rmutex_extension_abstract.h"
#include "read_write_mutex_extension_abstract.h"
namespace dlib namespace dlib
{ {
...@@ -24,7 +25,7 @@ namespace dlib ...@@ -24,7 +25,7 @@ namespace dlib
!*/ !*/
public: public:
auto_mutex ( explicit auto_mutex (
const mutex& m const mutex& m
); );
/*! /*!
...@@ -33,7 +34,7 @@ namespace dlib ...@@ -33,7 +34,7 @@ namespace dlib
- m will be locked - m will be locked
!*/ !*/
auto_mutex ( explicit auto_mutex (
const rmutex& m const rmutex& m
); );
/*! /*!
...@@ -42,6 +43,15 @@ namespace dlib ...@@ -42,6 +43,15 @@ namespace dlib
- m will be locked - m will be locked
!*/ !*/
explicit auto_mutex (
const read_write_mutex& m
);
/*!
ensures
- #*this is properly initialized
- m will be locked via m.lock() (i.e. a write lock will be obtained)
!*/
~auto_mutex ( ~auto_mutex (
); );
/*! /*!
...@@ -56,6 +66,44 @@ namespace dlib ...@@ -56,6 +66,44 @@ namespace dlib
auto_mutex& operator=(auto_mutex&); // assignment operator auto_mutex& operator=(auto_mutex&); // assignment operator
}; };
// ----------------------------------------------------------------------------------------
class auto_mutex_readonly
{
/*!
INITIAL VALUE
The mutex given in the constructor is locked and associated with this
object.
WHAT THIS OBJECT REPRESENTS
This object represents a mechanism for automatically locking and unlocking
a read_write_mutex object. In paricular, a readonly lock is used.
!*/
public:
explicit auto_mutex_readonly (
const read_write_mutex& m
);
/*!
ensures
- #*this is properly initialized
- a readonly lock will be obtained on m using m.lock_readonly()
!*/
~auto_mutex_readonly (
);
/*!
ensures
- all resources allocated by *this have been freed
- the mutex associated with *this has been unlocked
!*/
private:
// restricted functions
auto_mutex_readonly(auto_mutex_readonly&); // copy constructor
auto_mutex_readonly& operator=(auto_mutex_readonly&); // assignment operator
};
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "threads_kernel.h" #include "threads_kernel.h"
#include "rmutex_extension.h" #include "rmutex_extension.h"
#include "read_write_mutex_extension.h"
#include "auto_unlock_extension_abstract.h" #include "auto_unlock_extension_abstract.h"
namespace dlib namespace dlib
...@@ -20,27 +21,40 @@ namespace dlib ...@@ -20,27 +21,40 @@ namespace dlib
- the mutex pointed to by m is locked - the mutex pointed to by m is locked
- if (r != 0) then - if (r != 0) then
- the mutex pointed to by r is locked - the mutex pointed to by r is locked
- exactly one of r or m is not 0. - if (rw != 0) then
- the mutex pointed to by rw is locked
- exactly one of r, m, or rw is not 0.
CONVENTION CONVENTION
- if (m != 0) then - if (m != 0) then
- the mutex pointed to by m is locked - the mutex pointed to by m is locked
- if (r != 0) then - if (r != 0) then
- the mutex pointed to by r is locked - the mutex pointed to by r is locked
- exactly one of r or m is not 0. - if (rw != 0) then
- the mutex pointed to by rw is locked
- exactly one of r, m, or rw is not 0.
!*/ !*/
public: public:
auto_unlock ( explicit auto_unlock (
const mutex& m_ const mutex& m_
) : m(&m_), ) : m(&m_),
r(0) r(0),
rw(0)
{} {}
auto_unlock ( explicit auto_unlock (
const rmutex& r_ const rmutex& r_
) : m(0), ) : m(0),
r(&r_) r(&r_),
rw(0)
{}
explicit auto_unlock (
const read_write_mutex& rw_
) : m(0),
r(0),
rw(&rw_)
{} {}
~auto_unlock ( ~auto_unlock (
...@@ -48,20 +62,51 @@ namespace dlib ...@@ -48,20 +62,51 @@ namespace dlib
{ {
if (m != 0) if (m != 0)
m->unlock(); m->unlock();
else else if (r != 0)
r->unlock(); r->unlock();
else
rw->unlock();
} }
private: private:
const mutex* m; const mutex* m;
const rmutex* r; const rmutex* r;
const read_write_mutex* rw;
// restricted functions // restricted functions
auto_unlock(auto_unlock&); // copy constructor auto_unlock(auto_unlock&); // copy constructor
auto_unlock& operator=(auto_unlock&); // assignment operator auto_unlock& operator=(auto_unlock&); // assignment operator
}; };
// ----------------------------------------------------------------------------------------
class auto_unlock_readonly
{
public:
explicit auto_unlock_readonly (
const read_write_mutex& rw_
) :
rw(rw_)
{}
~auto_unlock_readonly (
)
{
rw.unlock_readonly();
}
private:
const read_write_mutex& rw;
// restricted functions
auto_unlock_readonly(auto_unlock_readonly&); // copy constructor
auto_unlock_readonly& operator=(auto_unlock_readonly&); // assignment operator
};
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "threads_kernel_abstract.h" #include "threads_kernel_abstract.h"
#include "rmutex_extension_abstract.h" #include "rmutex_extension_abstract.h"
#include "read_write_mutex_extension_abstract.h"
namespace dlib namespace dlib
{ {
...@@ -25,7 +26,7 @@ namespace dlib ...@@ -25,7 +26,7 @@ namespace dlib
!*/ !*/
public: public:
auto_unlock ( explicit auto_unlock (
const mutex& m const mutex& m
); );
/*! /*!
...@@ -34,7 +35,7 @@ namespace dlib ...@@ -34,7 +35,7 @@ namespace dlib
- does not modify m in any way - does not modify m in any way
!*/ !*/
auto_unlock ( explicit auto_unlock (
const rmutex& m const rmutex& m
); );
/*! /*!
...@@ -43,6 +44,15 @@ namespace dlib ...@@ -43,6 +44,15 @@ namespace dlib
- does not modify m in any way - does not modify m in any way
!*/ !*/
explicit auto_unlock (
const read_write_mutex& m
);
/*!
ensures
- #*this is properly initialized
- does not modify m in any way
!*/
~auto_unlock ( ~auto_unlock (
); );
/*! /*!
...@@ -57,6 +67,46 @@ namespace dlib ...@@ -57,6 +67,46 @@ namespace dlib
auto_unlock& operator=(auto_unlock&); // assignment operator auto_unlock& operator=(auto_unlock&); // assignment operator
}; };
// ----------------------------------------------------------------------------------------
class auto_unlock_readonly
{
/*!
INITIAL VALUE
The mutex given in the constructor is associated with this object.
WHAT THIS OBJECT REPRESENTS
This object represents a mechanism for automatically unlocking
a read_write_mutex object. It is useful when you already have a locked mutex
and want to make sure it gets unlocked even if an exception is thrown
or you quit the function at a weird spot. Note that the mutex
is unlocked by calling unlock_readonly() on it.
!*/
public:
explicit auto_unlock_readonly (
const read_write_mutex& m
);
/*!
ensures
- #*this is properly initialized
- does not modify m in any way
!*/
~auto_unlock_readonly (
);
/*!
ensures
- all resources allocated by *this have been freed
- calls unlock_readonly() on the mutex associated with *this
!*/
private:
// restricted functions
auto_unlock_readonly(auto_unlock_readonly&); // copy constructor
auto_unlock_readonly& operator=(auto_unlock_readonly&); // assignment operator
};
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
} }
......
// Copyright (C) 2010 Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_READ_WRITE_MUTEX_EXTENSIOn_
#define DLIB_READ_WRITE_MUTEX_EXTENSIOn_
#include "threads_kernel.h"
#include "read_write_mutex_extension_abstract.h"
namespace dlib
{
// ----------------------------------------------------------------------------------------
class read_write_mutex
{
/*!
INITIAL VALUE
- max_locks == defined by constructor
- available_locks == max_locks
- write_lock_in_progress == false
- write_lock_active == false
CONVENTION
- Each time someone gets a read only lock they take one of the "available locks"
and each write lock takes all possible locks (i.e. max_locks). The number of
available locks is recorded in available_locks. Any time you try to lock this
object and there aren't available locks you have to wait.
- max_locks == max_readonly_locks()
- if (some thread is on the process of obtaining a write lock) then
- write_lock_in_progress == true
- else
- write_lock_in_progress == false
- if (some thread currently has a write lock on this mutex) then
- write_lock_active == true
- else
- write_lock_active == false
!*/
public:
read_write_mutex (
) : s(m),
max_locks(0xFFFFFFFF),
available_locks(max_locks),
write_lock_in_progress(false),
write_lock_active(false)
{}
explicit read_write_mutex (
unsigned long max_locks_
) : s(m),
max_locks(max_locks_),
available_locks(max_locks_),
write_lock_in_progress(false),
write_lock_active(false)
{
// make sure requires clause is not broken
DLIB_ASSERT(max_locks > 0,
"\t read_write_mutex::read_write_mutex(max_locks)"
<< "\n\t You must give a non-zero value for max_locks"
<< "\n\t this: " << this
);
}
~read_write_mutex (
)
{}
void lock (
) const
{
m.lock();
// If another write lock is already in progress then wait for it to finish
// before we start trying to grab all the available locks. This way we
// don't end up fighting over the locks.
while (write_lock_in_progress)
s.wait();
// grab the right to perform a write lock
write_lock_in_progress = true;
// now start grabbing all the locks
unsigned long locks_obtained = available_locks;
available_locks = 0;
while (locks_obtained != max_locks)
{
s.wait();
locks_obtained += available_locks;
available_locks = 0;
}
write_lock_in_progress = false;
write_lock_active = true;
m.unlock();
}
void unlock (
) const
{
m.lock();
// only do something if there really was a lock in place
if (write_lock_active)
{
available_locks = max_locks;
write_lock_active = false;
s.broadcast();
}
m.unlock();
}
void lock_readonly (
) const
{
m.lock();
while (available_locks == 0)
s.wait();
--available_locks;
m.unlock();
}
void unlock_readonly (
) const
{
m.lock();
// If this condition is false then it means there are no more readonly locks
// to free. So we don't do anything.
if (available_locks != max_locks && !write_lock_active)
{
++available_locks;
// only perform broadcast when there is another thread that might be listening
if (available_locks == 1 || write_lock_in_progress)
{
s.broadcast();
}
}
m.unlock();
}
unsigned long max_readonly_locks (
) const
{
return max_locks;
}
private:
mutex m;
signaler s;
const unsigned long max_locks;
mutable unsigned long available_locks;
mutable bool write_lock_in_progress;
mutable bool write_lock_active;
// restricted functions
read_write_mutex(read_write_mutex&); // copy constructor
read_write_mutex& operator=(read_write_mutex&); // assignment operator
};
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_READ_WRITE_MUTEX_EXTENSIOn_
// Copyright (C) 2010 Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License See LICENSE.txt for the full license.
#undef DLIB_READWRITE_MUTEX_EXTENSIOn_ABSTRACT_
#ifdef DLIB_READWRITE_MUTEX_EXTENSIOn_ABSTRACT_
#include "threads_kernel_abstract.h"
namespace dlib
{
// ----------------------------------------------------------------------------------------
class read_write_mutex
{
/*!
INITIAL VALUE
read_write_mutex is in the fully unlocked state
WHAT THIS OBJECT REPRESENTS
This object represents a mutex intended to be used for synchronous
thread control of shared data. When a thread wants to access some
shared data it locks out other threads by calling lock() and calls
unlock() when it is finished.
This mutex also has the additional ability to distinguish between
a lock for the purposes of modifying some shared data, a write lock,
and a lock for the purposes of only reading shared data, a readonly
lock. The lock() and unlock() functions are used for write locks while
the lock_readonly() and unlock_readonly() are for readonly locks.
The difference between a readonly and write lock can be understood as
follows. The read_write_mutex will allow many threads to obtain simultaneous
readonly locks but will only allow a single thread to obtain a write lock.
Moreover, while the write lock is obtained no other threads are allowed
to have readonly locks.
!*/
public:
read_write_mutex (
);
/*!
ensures
- #*this is properly initialized
- max_readonly_locks() == 0xFFFFFFFF
(i.e. about 4 billion)
throws
- dlib::thread_error
the constructor may throw this exception if there is a problem
gathering resources to create the read_write_mutex.
!*/
explicit read_write_mutex (
unsigned long max_locks
);
/*!
requires
- max_locks > 0
ensures
- #*this is properly initialized
- max_readonly_locks() == max_locks
throws
- dlib::thread_error
the constructor may throw this exception if there is a problem
gathering resources to create the read_write_mutex.
!*/
~read_write_mutex (
);
/*!
requires
- *this is not locked
ensures
- all resources allocated by *this have been freed
!*/
void lock (
) const;
/*!
ensures
- if (there is any kind of lock on *this) then
- the calling thread is put to sleep until a write lock becomes available.
Once available, a write lock is obtained on this mutex and this function
terminates.
- else
- a write lock is obtained on this mutex and the calling thread is not put to sleep
!*/
void unlock (
) const;
/*!
ensures
- if (there is a write lock on *this) then
- #*this is unlocked (i.e. other threads may now lock this object)
- else
- the call to unlock() has no effect
!*/
unsigned long max_readonly_locks (
) const;
/*!
ensures
- returns the maximum number of concurrent readonly locks this object will allow.
!*/
void lock_readonly (
) const;
/*!
ensures
- if (there is a write lock on *this or there are no free readonly locks) then
- the calling thread is put to sleep until there is no longer a write lock
and a free readonly lock is available. Once this is the case, a readonly
lock is obtained and this function terminates.
- else
- a readonly lock is obtained on *this and the calling thread is not put
to sleep. Note that multiple readonly locks can be obtained at once.
!*/
void unlock_readonly (
) const;
/*!
ensures
- if (there is a readonly lock on *this) then
- one readonly lock is removed from *this.
- else
- the call to unlock_readonly() has no effect.
!*/
private:
// restricted functions
read_write_mutex(read_write_mutex&); // copy constructor
read_write_mutex& operator=(read_write_mutex&); // assignment operator
};
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_READWRITE_MUTEX_EXTENSIOn_ABSTRACT_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment