From d9a4891a0ab22a0b794f232ba6f88e21a820c17e Mon Sep 17 00:00:00 2001 From: Mustafa Kemal GILOR Date: Wed, 2 Dec 2020 21:37:46 +0300 Subject: [PATCH] C++20 update, concurrent library is now abstracted from lock and lockable implementation. --- CMakeLists.txt | 3 +- README.md | 25 +-- concurrent.hpp | 354 ++++++++++++++++++++++++++++++++++++++++ concurrent_boost.hpp | 29 ++++ concurrent_resource.hpp | 247 ---------------------------- concurrent_stl.hpp | 25 +++ main.cpp | 72 ++++---- 7 files changed, 455 insertions(+), 300 deletions(-) create mode 100644 concurrent.hpp create mode 100644 concurrent_boost.hpp delete mode 100644 concurrent_resource.hpp create mode 100644 concurrent_stl.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 971154d..cc675be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,8 @@ project(lockable VERSION 0.1.0) include(CTest) enable_testing() -set (CMAKE_CXX_STANDARD 17) +set (CMAKE_CXX_STANDARD 20) +set (CMAKE_CXX_STANDARD_REQUIRED true) add_executable(lockable main.cpp) target_link_libraries(lockable -lpthread) diff --git a/README.md b/README.md index 188b001..1bcd1f0 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,17 @@ Accessors are simple types, bundled with a reference to shared object, and a loc accessor's type (read,write). As they contain lock object internally, the lock is held during entire life of an accessor object's lifetime. ~~~cpp -#include "concurrent_resource.hpp" +#include "concurrent_stl.hpp" // or concurrent_boost.hpp #include #include // A very-basic example int main(void){ // Wrap a regular vector to make it thread-safe - mkg::concurrent_resource> books; + mkg::concurrent> books; { // Grab a write accessor (we can manipulate the vector through it) - auto writer = books.write_accessor(); + auto writer = books.write_accessor_handle(); // Now, an exclusive lock is held upon the construction of write accessor // by std::unique_lock(...) (or boost::) // We can reach the underlying vector by just ->'ing the writer. @@ -36,7 +36,7 @@ int main(void){ // the lock is released. { // Grab a read accessor (read-only, cannot modify the vector through it) - auto reader = books.read_accessor(); + auto reader = books.read_accessor_handle(); // Treat the accessors as if they're pointers to the underlying resource // (or std|boost::optional's if you will) for(const auto & book : (*reader)){ @@ -49,15 +49,13 @@ int main(void){ dependencies? ------------ -If you are working with the latest standard library (C++17), then you have nothing to worry about. Just grab and include the header. -For before C++17, library currently depends on BOOST to function. You can make it work with BOOST implementation instead in C++17 standard via defining -`MKG_CONCURRENT_RESOURCE_USE_BOOST_SHARED_MUTEX` macro before including the header. +C++20 is required as project now uses `concepts`. how to compile? ------------ As this is a `header-only` library, you do not need to do anything special. Just include `concurrent_resource.hpp` and you'll be fine. To compile `main.cpp`, which contains the examples, you need `CMake` (if you're lazy), or just type -* C++17(std): g++ main.cpp -lpthread -o example -* BOOST : g++ main.cpp -lpthread -lboost_system -lboost_thread -o example +* C++20(std): g++ main.cpp -lpthread -o example --std=c++2a +* BOOST : g++ main.cpp -lpthread -lboost_system -lboost_thread -o example --std=c++2a in source folder. (or equivalent in Windows) anything to worry about? @@ -78,10 +76,15 @@ cons * std::shared_mutex & boost::shared_mutex are not known with their blow-your-socks-off performance (but they'll do `just fine ™` ) :shipit: +what is the difference from the first release? +------------ +- Concurrent type is now fully generic, you can now implement your custom Lockable and Lock types and use it with concurrent wrapper +- Concept validation (via C++20 concepts) +- Simplifications + future plans ------------ -* Remove BOOST dependency for pre-C++17 -* Add ability to prefer SRWLOCK instead of std::shared_mutex in Windows platform (see discussion at [stackoverflow](https://stackoverflow.com/questions/13206414/why-slim-reader-writer-exclusive-lock-outperformance-the-shared-one) ) +* well, most of the future plans are now obsolete, because this new release is abstract from any lock implementation. license ------------ diff --git a/concurrent.hpp b/concurrent.hpp new file mode 100644 index 0000000..e7c2eed --- /dev/null +++ b/concurrent.hpp @@ -0,0 +1,354 @@ +/** + * ______________________________________________________ + * Header-only concurrency wrapper. + * + * @file concurrent.hpp + * @author Mustafa Kemal GILOR + * @date 02.12.2020 + * + * SPDX-License-Identifier: MIT + * ______________________________________________________ + */ + +#pragma once + +#include +#include + +namespace mkg { + + /** + * Checks whether given type T satisfies the requirements of + * `BasicLockable` concept (a.k.a named requirement). + * + * @see http://www.cplusplus.com/reference/concept/BasicLockable/ + * + * @tparam T Type to check + */ + template + concept basic_lockable = requires(T a) { + + /** + * @brief Instantiation of type T must have a public function named `lock` + * and it must have return type of `void`. + */ + { a.lock() } -> std::same_as; + + /** + * @brief Instantiation of type T must have a public function named `unlock` + * and it must have return type of `void`, and it must be noexcept. + */ + { a.unlock() } -> std::same_as; + }; + + /** + * Checks whether given type T satisfies the requirements of + * `Lockable` concept (a.k.a named requirement). + * + * @see http://www.cplusplus.com/reference/concept/Lockable/ + * + * @tparam T Type to check + */ + template + concept lockable = basic_lockable && requires (T a){ + /** + * @brief Instantiation of type T must have a public function named `try_lock` + * and it must have return type of `bool`. + */ + { a.try_lock() } -> std::same_as; + }; + + + /** + * Checks whether given type T satisfies the requirements of + * `TimedLockable` concept (a.k.a named requirement). + * + * @see http://www.cplusplus.com/reference/concept/TimedLockable/ + * + * @tparam T Type to check + */ + template + concept timed_lockable = lockable && requires (T a) { + /** + * @brief Instantiation of type T must have a public function named `try_lock_for` + * which accepts std::chrono::duration<> as its argumentand it must have return type + * of `bool`. + */ + { a.try_lock_for(std::chrono::seconds{}) } -> std::same_as; + /** + * @brief Instantiation of type T must have a public function named `try_lock_until` + * which accepts std::chrono::time_point<> as its argumentand it must have return type + * of `bool`. + */ + { a.try_lock_until(std::chrono::time_point{ std::chrono::seconds{} }) } -> std::same_as; + }; + + /** + * Checks whether given type T satisfies the requirements of + * `basic_shared_lockable` concept (a.k.a named requirement). + * + * ** There are no official named requirements for this **. + * + * @tparam T Type to check + */ + template + concept basic_shared_lockable = basic_lockable && requires (T a) { + + /** + * @brief Instantiation of type T must have a public function named `lock_shared` + * it must have return type of `void`. + */ + { a.lock_shared() } -> std::same_as; + + /** + * @brief Instantiation of type T must have a public function named `unlock_shared` + * and it must have return type of `void`, and it must be noexcept. + */ + { a.unlock_shared() } -> std::same_as; + + }; + + + /** + * Checks whether given type T satisfies the requirements of + * `shared_lockable` concept (a.k.a named requirement). + * + * ** There are no official named requirements for this **. + * + * @tparam T Type to check + */ + template + concept shared_lockable = basic_shared_lockable && requires (T a) { + + /** + * @brief Instantiation of type T must have a public function named `try_lock_shared` + * and it must have return type of `bool`. + */ + { a.try_lock_shared() } -> std::same_as; + + }; + + + /** + * Checks whether given type T satisfies the requirements of + * `shared_timed_lockable` concept (a.k.a named requirement). + * + * ** There are no official named requirements for this **. + * + * @tparam T Type to check + */ + template + concept shared_timed_lockable = shared_lockable && requires (T a) { + + /** + * @brief Instantiation of type T must have a public function named `try_lock_shared_for` + * which accepts std::chrono::duration<> as its argumentand it must have return type + * of `bool`. + */ + { a.try_lock_shared_for(std::chrono::seconds{}) } -> std::same_as; + + /** + * @brief Instantiation of type T must have a public function named `try_lock_shared_until` + * which accepts std::chrono::time_point<> as its argumentand it must have return type + * of `bool`. + */ + { a.try_lock_shared_until(std::chrono::time_point{ std::chrono::seconds{} }) } -> std::same_as; + + }; + + /** + * A base class to make derived class non-copyable. + * + * Inherit from this class to in order to make derived class noncopyable. + * By non-copyable, it means the following operations are explicitly removed from the derived; + * + * Copy-construction from mutable lvalue reference + * Copy-construction from immutable lvalue reference + * Copy-assignment from mutable lvalue reference + * Copy-assignment from immutable lvalue reference + */ + class noncopyable{ + noncopyable(noncopyable&) = delete; + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; + public: + noncopyable() = default; + ~noncopyable() = default; + noncopyable(noncopyable&&) = default; + noncopyable& operator=(noncopyable&&) = default; + }; + + /** + * A RAII wrapper type which wraps a resource and its' associated lock. The wrapper locks given `Lockable` object + * via instantiating a `LockType` object upon construction. This `LockType` object and the resource will be held + * as a class member together. This will guarantee that acquired type of lock will not be released until the wrapper + * object's destructor is called. + * + * The wrapper has "*" and "->" operator overloads to provide access wrapped resource. If wrapped resource + * type is a class type, "->" operator will allow client to access its` members directly. The (*) operator + * will return cv-qualified reference to the wrapped object. + * + * @tparam TypeQualifiedNonConcurrentType CV qualified type of the wrapped resource. This will determine the access level. + * @tparam LockableType Type of the `Lockable` object + * @tparam LockType Type of the `Lock` object to lock the `Lockable` type + */ + template typename LockType> + requires (std::is_reference_v) + class accessor : private noncopyable { + public: + + /** + * @brief Accessor object constructor + * + * @param resource Resource to grant desired access to + * @param lockable Lockable, which will be locked during the access + */ + constexpr explicit accessor(LockableType& lockable, TypeQualifiedNonConcurrentType resource) + requires (!std::is_const_v) + : lock(lockable), locked_resource(resource) + {} + + /** + * @brief Class member access operator overload to behave as if + * instance of `accessor` class is a `locked_resource` pointer. + * + * @note This operator has a special condition. + * + * Quoting from C++98 standard §13.5.6/1 "Class member access": + * + * "An expression x->m is interpreted as (x.operator->())->m for a class + * object x of type T if T::operator-> exists and if the operator is selected + * at the best match function by the overload resolution mechanism (13.3)." + * + * which basically means when we call this operator, we will access member functions + * of `TypeQualifiedNonConcurrentType`. + * + * @return Provides member access if `TypeQualifiedNonConcurrentType` is a class type. Otherwise, it has no use. + */ + inline decltype(auto) operator->() noexcept { + struct member_access_operator_decorator { + constexpr explicit member_access_operator_decorator(TypeQualifiedNonConcurrentType value) + : value(value) + {} + constexpr decltype(auto) operator->() const noexcept { return &value; } + private: + TypeQualifiedNonConcurrentType value; + }; + + return member_access_operator_decorator { locked_resource }; + } + + /** + * Dereference (star) operator overload + * + * @return cv-qualified reference to the resource + */ + inline decltype(auto) operator*() noexcept { return locked_resource; } + private: + LockType lock; + TypeQualifiedNonConcurrentType locked_resource; + }; + + template typename LockType> + using shared_accessor = accessor>, LockableType, LockType>; + + template typename LockType> + using exclusive_accessor = accessor, LockableType, LockType>; + + /** + * A wrapper type to protect a `NonConcurrentType` via locking a `LockableType` + * via; + * + * - `SharedLockType` when read only access + * - `ExclusiveLockType` when read & write access + * + * is requested. + * + * @tparam NonConcurrentType A non thread-safe type to wrap + * @tparam LockableType A type which satisfies the `BasicSharedLockable` named requirement (eg. std::shared_mutex) + * @tparam SharedLockType A RAII type which will be used to lock the `Lockable` when read access is requested (eg. std::shared_lock) + * @tparam ExclusiveLockType A RAII type which will be used to lock the `Lockable` when write access is requested (eg. std::unique_lock) + */ + template typename SharedLockType, + template typename ExclusiveLockType > + class concurrent { + public: + using shared_accessor_t = shared_accessor; + using exclusive_accessor_t = exclusive_accessor; + + /** + * @brief Default constructor + * + * Instantiates the `NonConcurrentType` by invoking its' default constructor. + * + * @exception noexcept if `NonConcurrentType` is `NoThrowDefaultConstructible` + */ + concurrent() + noexcept(std::is_nothrow_default_constructible_v) + requires (std::is_default_constructible_v) + : resource{} + {} + + /** + * @brief Copy constructor (from wrapped type) + * + * Instantiates the `NonConcurrentType` by invoking its' copy constructor. + * + * @exception noexcept if `NonConcurrentType` is `NoThrowCopyConstructible` + */ + explicit concurrent(const NonConcurrentType& value) + noexcept(std::is_nothrow_copy_constructible_v) + requires (std::is_copy_constructible_v) + : resource(value) + {} + + /** + * @brief Move constructor (from wrapped type) + * + * Instantiates the `NonConcurrentType` by invoking its' move constructor. + * + * @exception noexcept if `NonConcurrentType` is `NoThrowMoveConstructible` + */ + explicit concurrent(NonConcurrentType&& value) + noexcept(std::is_nothrow_move_constructible_v) + requires (std::is_move_constructible_v) + : resource(std::move(value)) + {} + + /** + * @brief Destructor + * + * @exception noexcept if `NonConcurrentType` is `NoThrowDestructible` + */ + ~concurrent() noexcept(std::is_nothrow_destructible_v) = default; + + + /** + * @brief Get read-only (shared) access to underlying wrapped object. Access object will guarantee + * `Lockable` object will be locked via `SharedLockType` from beginning to end of the + * returned accessor object. + * + * @return shared_accessor_t Read-only (shared) accessor object to the wrapped object + */ + decltype(auto) read_access_handle() const noexcept { return shared_accessor_t{ lockable, resource }; } + + /** + * @brief Get write (exclusive) access to underlying wrapped object. Access object will guarantee + * `Lockable` object will be locked via `ExclusiveLockType` from beginning to end of the + * returned accessor object. + * + * @exception Exception-safe + * + * @return exclusive_accessor_t Read & write (exclusive) accessor object to the wrapped object + */ + decltype(auto) write_access_handle() noexcept { return exclusive_accessor_t{ lockable, resource }; } + + private: + NonConcurrentType resource; + mutable LockableType lockable; + }; + + +} \ No newline at end of file diff --git a/concurrent_boost.hpp b/concurrent_boost.hpp new file mode 100644 index 0000000..09305c8 --- /dev/null +++ b/concurrent_boost.hpp @@ -0,0 +1,29 @@ +/** + * ______________________________________________________ + * Concurrent wrapper, defaulted with Boost lock primitives. + * + * @file concurrent_stl.hpp + * @author Mustafa Kemal GILOR + * @date 02.12.2020 + * + * SPDX-License-Identifier: MIT + * ______________________________________________________ + */ + +#pragma once + +#include "concurrent.hpp" + +#if defined __has_include +# if __has_include () +# include +namespace mkg { + template typename SharedLockType = boost::shared_lock, + template typename ExclusiveLockType = boost::unique_lock > + class concurrent; +} +# else +# error "Boost implementation of concurrent wrapper requires boost/thread/shared_mutex.hpp to be available." +# endif +#endif diff --git a/concurrent_resource.hpp b/concurrent_resource.hpp deleted file mode 100644 index cea9a58..0000000 --- a/concurrent_resource.hpp +++ /dev/null @@ -1,247 +0,0 @@ -/** -* \file concurrent_resource.hpp -* \author Mustafa Kemal GILOR -* \date 05/01/2019 18:41 PM -* -* \brief -* A header-only library that provides thread-safety for arbitrary kind of resources by wrapping them with -* thread synchronization primitives transparently. The API exposes small objects known as `accessors`, -* enabling user to access resources concurrently in RAII-style lifetime management. As long as accessor -* object is alive, the user can access the resource concurrently, without worrying about the thread -* synchronization. All thread synchronization related operation is done in background, away from user's -* perspective. -* -* The concept is robust, makes it easier to grant thread safety to any kind of resource without adding -* too much overhead. -* -* The library either requires C++17, or BOOST to function. -* -* \copyright -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), -* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -* -* \disclaimer -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -* IN THE SOFTWARE. -*/ - - - -#if !defined(MKG_CONCURRENT_RESOURCE_USE_BOOST_SHARED_MUTEX) && (__cplusplus >= 201703L) -#include // prerequisite : C++17 -namespace mkg{ - using shared_mutex_t = std::shared_mutex; - using read_lock = std::shared_lock; - using write_lock = std::unique_lock; -} -#define MG_LOCKABLE_USING_STD -#else -#include -namespace mkg{ - using shared_mutex_t = boost::shared_mutex; - using read_lock = boost::shared_lock; - using write_lock = boost::unique_lock; - // boost's rwlock implementation has two additional lock kinds, which can be useful - // in several scenarios. - using upgrade_lock = boost::upgrade_lock; - using upgrade_to_write_lock = boost::upgrade_to_unique_lock; -} -#define MG_LOCKABLE_USING_BOOST -#endif - -namespace mkg{ - - /** - * @brief Library implementation details. - * - * @warning The code/class/api in this namespace might change drastically over the time. - * (not reliable) - */ - namespace detail{ - - /** - * @brief This is a simple workaround for the member access operator overload behaviour. - * By default, member access operator requires return value to be `dereferenceable`, meaning - * that, the return type must be a pointer to an object. As we intend to return - * references from our accessors, this behaviour prevents us to do so. - * - * As a simple solution, we wrap our real type in our decorator class, exposing type pointer to - * only wrapper (in our scenario, wrapper would be accessor class). The wrapper itself returns - * this decorator by value, resulting the desired behaviour. - * - * @tparam TypeToDecorate Type to add member access operator to. - */ - template - struct member_access_operator_decorator{ - constexpr member_access_operator_decorator(TypeToDecorate value) - : value(value) - {} - constexpr decltype(auto) operator->() const noexcept { return &value; } - private: - TypeToDecorate value; - }; - - - /** - * @brief Wraps a resource with lock(read,write,upgrade), leaving only desired - * access level to the resource. The lock is acquired on construction, released on - * destruction of `accessor' type. - * - * To access the resource, just treat `accessor` object as a pointer to - * the resource. The class has similar semantics with std|boost::optional. - * @tparam LockType Lock type indicating the desired access (read|write|upgrade) - * @tparam ResourceType Type of the resource to wrap with desired access - */ - template - struct accessor{ - using base_t = accessor; - - /** - * @brief Construct a new accessor object - * - * @param resource Resource to grant desired access to - * @param mtx Resource's read-write lock - */ - constexpr accessor(ResourceType resource, shared_mutex_t & mtx) - : lock(mtx), resource(resource) - {} - - constexpr member_access_operator_decorator operator->() noexcept - { - return member_access_operator_decorator(resource); - } - - decltype(auto) operator*() noexcept {return resource; } - - private: - LockType lock; - ResourceType resource; - }; - - /** - * @brief Non-thread safe accessor to the resource. - * - * @tparam ResourceType Resource to provide non-thread safe access. - */ - template - struct unsafe_accessor { - constexpr unsafe_accessor(ResourceType resource) - : resource(resource) - {} - constexpr member_access_operator_decorator operator->() noexcept - { - return member_access_operator_decorator(resource); - } - private: - ResourceType resource; - }; - - template - using read_accessor_base = accessor ; - - template - using write_accessor_base = accessor; - - template - using unsafe_read_accessor_base = unsafe_accessor ; - - template - using unsafe_write_accessor_base = unsafe_accessor; - - - template - struct read_accessor : public read_accessor_base - { - using read_accessor_base::read_accessor_base; - }; - - template - struct write_accessor : public write_accessor_base - { - using write_accessor_base::write_accessor_base; - }; - - - template - struct unsafe_read_accessor : public unsafe_read_accessor_base - { - using unsafe_read_accessor_base::unsafe_read_accessor_base; - }; - - template - struct unsafe_write_accessor : public unsafe_write_accessor_base - { - using unsafe_write_accessor_base::unsafe_write_accessor_base; - }; - } - - /** - * @brief A concept, which adds a read-write lock - * to inheriting class. - */ - template - struct lockable{ - protected: - mutable SharedMutexType rwlock; - }; - - - /** - * @brief This class wraps a resource, and exposes accessors only as an interface. - * The only way to access the underlying resource is, through the accessors. The accessors - * which are not prefixed with `unsafe_` are all thread-safe and all concurrent operations - * related to them can be executed safely. - * - * @tparam ResourceType Resource type to wrap - */ - template - struct concurrent_resource : lockable<>{ - public: - /** - * @brief Grab a read-only, thread-safe accessor to the resource. - * Concurrent readers using read accessor may access the protected resource. Write - * accessors can not access to the resource whilst a read accessor is performing. - * (the opposite is valid as well) - * - * @return detail::read_accessor Read-only accessor to the resource. - */ - detail::read_accessor read_access() - const noexcept { return {resource, rwlock}; } - - /** - * @brief Grab an exclusive (read-write), thread safe accessor to the resource. All subsequent - * read-write access requests will wait until the grabbed write access object is released. - * - * @return detail::write_accessor Exclusive accessor to the resource. - */ - detail::write_accessor write_access() - noexcept { return {resource, rwlock}; } - - /** - * @brief Grab a read-only, NON-THREAD SAFE accessor to the resource. - * Note that this does not acquire any thread synchronization primitives. - * - * @return detail::read_accessor Unsafe, read-only accessor to the resource. - */ - detail::unsafe_read_accessor unsafe_read_access() - const noexcept { return {resource}; } - /** - * @brief Grab a read & write, NON-THREAD SAFE accessor to the resource. - * Note that this does not acquire any thread synchronization primitives. - * @return detail::write_accessor Unsafe, read & write accessor to the resource. - */ - detail::unsafe_write_accessor unsafe_write_access() - noexcept { return {resource}; } - private: - /** - * @brief The underlying resource. - * The con - * - */ - ResourceType resource; - }; -} \ No newline at end of file diff --git a/concurrent_stl.hpp b/concurrent_stl.hpp new file mode 100644 index 0000000..4fa77ff --- /dev/null +++ b/concurrent_stl.hpp @@ -0,0 +1,25 @@ +/** + * ______________________________________________________ + * Concurrent wrapper, defaulted with STL lock primitives. + * + * @file concurrent_stl.hpp + * @author Mustafa Kemal GILOR + * @date 02.12.2020 + * + * SPDX-License-Identifier: MIT + * ______________________________________________________ + */ + +#pragma once + +#include "concurrent.hpp" + +#include +#include + +namespace mkg { + template typename SharedLockType = std::shared_lock, + template typename ExclusiveLockType = std::unique_lock > + class concurrent; +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index a69e30e..39f21e6 100644 --- a/main.cpp +++ b/main.cpp @@ -1,26 +1,14 @@ /** -* \file main.cpp -* \author Mustafa Kemal GILOR -* \date 05/01/2019 18:41 PM -* -* \brief -* A simple program to illustrate concurrent_resource library's usage. -* -* \copyright -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), -* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -* -* \disclaimer -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -* IN THE SOFTWARE. -*/ - - - + * ______________________________________________________ + * A simple program to illustrate concurrent library's usage. + * + * @file concurrent_stl.hpp + * @author Mustafa Kemal GILOR + * @date 02.12.2020 + * + * SPDX-License-Identifier: MIT + * ______________________________________________________ + */ #include // std::array #include // std::vector @@ -32,7 +20,9 @@ #include // std::atomic #include // std::chrono -#include "concurrent_resource.hpp" + +//#include "concurrent_stl.hpp" // use stl lock primitives as backend +#include "concurrent_boost.hpp" // use stl lock primitives as backend using namespace mkg; @@ -46,13 +36,13 @@ int main(void){ // Supports arbitrary types, including standard library containers, user defined types // primitive types, pointer types.. { - concurrent_resource> concurrent_vector; + concurrent> concurrent_vector; { { // scopes are for limiting the accessor's lifetime - auto write_accessor = concurrent_vector.write_access(); + auto write_accessor = concurrent_vector.write_access_handle(); // We can safely access to the underlying vector now write_accessor->emplace_back("you can treat the accessor's as a pointer to underlying resource."); } @@ -61,7 +51,7 @@ int main(void){ #if __cplusplus >= 201703L // can alternatively done neater, if C++17 is available - if(auto wa = concurrent_vector.write_access(); !wa->empty()){ + if(auto wa = concurrent_vector.write_access_handle(); !wa->empty()){ wa->emplace_back("C++17 is awesome."); } #endif @@ -71,7 +61,7 @@ int main(void){ { // Grab a read-only accessor to the vector - auto read_accessor = concurrent_vector.read_access(); + auto read_accessor = concurrent_vector.read_access_handle(); // We now hold a read lock to the object // Iterate over the vector safely for(const auto & str : (*read_accessor)){ @@ -81,23 +71,23 @@ int main(void){ // Read accessor is gone, held lock is released } { - concurrent_resource> concurrent_map; + concurrent> concurrent_map; { - auto write_accessor = concurrent_map.write_access(); + auto write_accessor = concurrent_map.write_access_handle(); write_accessor->insert(std::make_pair("First", 1)); write_accessor->emplace("First", 1); } } { - concurrent_resource concurrent_string; + concurrent concurrent_string; { - auto write_accessor = concurrent_string.write_access(); + auto write_accessor = concurrent_string.write_access_handle(); (*write_accessor) = "this is awesome"; std::cout << (*write_accessor) << std::endl; } { - auto read_accessor = concurrent_string.read_access(); + auto read_accessor = concurrent_string.read_access_handle(); // (*read_accessor) = "this is not possible"; std::cout << (*read_accessor) << std::endl; } @@ -106,8 +96,8 @@ int main(void){ { { // Declare a concurrent resource pointer - concurrent_resource> concurrent_resource; - auto write_access = concurrent_resource.write_access(); + concurrent> concurrent; + auto write_access = concurrent.write_access_handle(); (*write_access) = std::make_unique(); // or @@ -125,17 +115,17 @@ int main(void){ { // This approach is syntatically simpler. - std::unique_ptr> cr; - cr = std::make_unique>(); + std::unique_ptr> cr; + cr = std::make_unique>(); - auto write_access = cr->write_access(); + auto write_access = cr->write_access_handle(); write_access->coefficient = 0.1; } } { - concurrent_resource> shared_resource; + concurrent> shared_resource; std::vector producer_threads, consumer_threads; static std::atomic idx = {0}; { @@ -144,7 +134,7 @@ int main(void){ [&shared_resource](){ for(;;){ { - auto write_accessor = shared_resource.write_access(); + auto write_accessor = shared_resource.write_access_handle(); write_accessor->emplace(std::to_string(idx++), "foo"); } std::this_thread::sleep_for(std::chrono::milliseconds(750)); @@ -160,14 +150,14 @@ int main(void){ for(;;){ { - auto read_accessor = shared_resource.read_access(); + auto read_accessor = shared_resource.read_access_handle(); for(const auto & pair : (*read_accessor)){ std::cout << pair.first << ":" << pair.second << std::endl; } std::flush(std::cout); } { - auto write_accessor = shared_resource.write_access(); + auto write_accessor = shared_resource.write_access_handle(); if(!write_accessor->empty()) write_accessor->erase(write_accessor->begin()); }