You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.4 KiB
206 lines
6.4 KiB
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <mutex>
|
|
|
|
namespace android::mediametrics {
|
|
|
|
/**
|
|
* Wraps a shared-ptr for which member access through operator->() behaves
|
|
* as if the shared-ptr is atomically copied and then (without a lock) -> called.
|
|
*
|
|
* See related C++ 20:
|
|
* https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2
|
|
*
|
|
* EXAMPLE:
|
|
*
|
|
* SharedPtrWrap<T> t{};
|
|
*
|
|
* thread1() {
|
|
* t->func(); // safely executes either the original t or the one created by thread2.
|
|
* }
|
|
*
|
|
* thread2() {
|
|
* t.set(std::make_shared<T>()); // overwrites the original t.
|
|
* }
|
|
*/
|
|
template <typename T>
|
|
class SharedPtrWrap {
|
|
mutable std::mutex mLock;
|
|
std::shared_ptr<T> mPtr;
|
|
|
|
public:
|
|
template <typename... Args>
|
|
explicit SharedPtrWrap(Args&&... args)
|
|
: mPtr(std::make_shared<T>(std::forward<Args>(args)...))
|
|
{}
|
|
|
|
/**
|
|
* Gets the current shared pointer. This must return a value, not a reference.
|
|
*
|
|
* For compatibility with existing shared_ptr, we do not pass back a
|
|
* shared_ptr<const T> for the const getter.
|
|
*/
|
|
std::shared_ptr<T> get() const {
|
|
std::lock_guard lock(mLock);
|
|
return mPtr;
|
|
}
|
|
|
|
/**
|
|
* Sets the current shared pointer, returning the previous shared pointer.
|
|
*/
|
|
std::shared_ptr<T> set(std::shared_ptr<T> ptr) { // pass by value as we use swap.
|
|
std::lock_guard lock(mLock);
|
|
std::swap(ptr, mPtr);
|
|
return ptr;
|
|
}
|
|
|
|
/**
|
|
* Returns a shared pointer value representing T at the instant of time when
|
|
* the call executes. The lifetime of the shared pointer will
|
|
* be extended as we are returning an instance of the shared_ptr
|
|
* not a reference to it. The destructor to the returned shared_ptr
|
|
* will be called sometime after the expression including the member function or
|
|
* the member variable is evaluated. Do not change to a reference!
|
|
*/
|
|
|
|
// For compatibility with existing shared_ptr, we do not pass back a
|
|
// shared_ptr<const T> for the const operator pointer access.
|
|
std::shared_ptr<T> operator->() const {
|
|
return get();
|
|
}
|
|
/**
|
|
* We do not overload operator*() as the reference is not stable if the
|
|
* lock is not held.
|
|
*/
|
|
};
|
|
|
|
/**
|
|
* Wraps member access to the class T by a lock.
|
|
*
|
|
* The object T is constructed within the LockWrap to guarantee
|
|
* locked access at all times. When T's methods are accessed through ->,
|
|
* a monitor style lock is obtained to prevent multiple threads from executing
|
|
* methods in the object T at the same time.
|
|
* Suggested by Kevin R.
|
|
*
|
|
* EXAMPLE:
|
|
*
|
|
* // Accumulator class which is very slow, requires locking for multiple threads.
|
|
*
|
|
* class Accumulator {
|
|
* int32_t value_ = 0;
|
|
* public:
|
|
* void add(int32_t incr) {
|
|
* const int32_t temp = value_;
|
|
* sleep(0); // yield
|
|
* value_ = temp + incr;
|
|
* }
|
|
* int32_t get() { return value_; }
|
|
* };
|
|
*
|
|
* // We use LockWrap on Accumulator to have safe multithread access.
|
|
* android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
|
|
*
|
|
* // Conversely, the following line fails:
|
|
* // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
|
|
*
|
|
* constexpr size_t THREADS = 100;
|
|
* constexpr size_t ITERATIONS = 10;
|
|
* constexpr int32_t INCREMENT = 1;
|
|
*
|
|
* // Test by generating multiple threads, all adding simultaneously.
|
|
* std::vector<std::future<void>> threads(THREADS);
|
|
* for (size_t i = 0; i < THREADS; ++i) {
|
|
* threads.push_back(std::async(std::launch::async, [&] {
|
|
* for (size_t j = 0; j < ITERATIONS; ++j) {
|
|
* a->add(INCREMENT); // add needs locked access here.
|
|
* }
|
|
* }));
|
|
* }
|
|
* threads.clear();
|
|
*
|
|
* // If the add operations are not atomic, value will be smaller than expected.
|
|
* ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
|
|
*
|
|
*/
|
|
template <typename T>
|
|
class LockWrap {
|
|
/**
|
|
* Holding class that keeps the pointer and the lock.
|
|
*
|
|
* We return this holding class from operator->() to keep the lock until the
|
|
* method function or method variable access is completed.
|
|
*/
|
|
class LockedPointer {
|
|
friend LockWrap;
|
|
LockedPointer(T *t, std::recursive_mutex *lock, std::atomic<size_t> *recursionDepth)
|
|
: mT(t), mLock(*lock), mRecursionDepth(recursionDepth) { ++*mRecursionDepth; }
|
|
|
|
T* const mT;
|
|
std::lock_guard<std::recursive_mutex> mLock;
|
|
std::atomic<size_t>* mRecursionDepth;
|
|
public:
|
|
~LockedPointer() {
|
|
--*mRecursionDepth; // Used for testing, we do not check underflow.
|
|
}
|
|
|
|
const T* operator->() const {
|
|
return mT;
|
|
}
|
|
T* operator->() {
|
|
return mT;
|
|
}
|
|
};
|
|
|
|
// We must use a recursive mutex because the end of the full expression may
|
|
// involve another reference to T->.
|
|
//
|
|
// A recursive mutex allows the same thread to recursively acquire,
|
|
// but different thread would block.
|
|
//
|
|
// Example which fails with a normal mutex:
|
|
//
|
|
// android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}};
|
|
// const int sum = v->operator[](0) + v->operator[](1);
|
|
//
|
|
mutable std::recursive_mutex mLock;
|
|
mutable T mT;
|
|
mutable std::atomic<size_t> mRecursionDepth{}; // Used for testing.
|
|
|
|
public:
|
|
template <typename... Args>
|
|
explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {}
|
|
|
|
const LockedPointer operator->() const {
|
|
return LockedPointer(&mT, &mLock, &mRecursionDepth);
|
|
}
|
|
LockedPointer operator->() {
|
|
return LockedPointer(&mT, &mLock, &mRecursionDepth);
|
|
}
|
|
|
|
// Returns the lock depth of the recursive mutex.
|
|
// @TestApi
|
|
size_t getRecursionDepth() const {
|
|
return mRecursionDepth;
|
|
}
|
|
};
|
|
|
|
} // namespace android::mediametrics
|