简体   繁体   English

同步线程安全的API,用于公开缓存的数据

[英]Synchronous thread safe APIs for exposing cached data

We offer a package which interfaces with a bunch of other packages who's APIs are not thread safe. 我们提供了一个可以与其他API线程不安全的其他软件包接口的软件包。 Our package's API is entirely message based and therefore asynchronous to allow thread safety for users of our package. 我们程序包的API完全基于消息,因此是异步的,从而为我们程序包的用户提供线程安全。 Our package therefore wraps a bunch of non-thread safe packages and offers a thread-safe API. 因此,我们的程序包包装了一堆非线程安全的程序包,并提供了线程安全的API。 That means that users of our package can interface with our package from any thread. 这意味着我们包的用户可以从任何线程与我们包进行交互。

We would like to add synchronous APIs to our package while maintaining thread safety. 我们希望在保持线程安全的同时将同步API添加到我们的包中。 I've done some research and have come up with two possible patterns to do this which I will share below. 我已经做过一些研究,并提出了两种可能的模式来做这件事,我将在下面分享。 I'm not entirely happy with either approach so am wondering if the community may have more suggestions for patterns we can use. 我对这两种方法都不完全满意,因此想知道社区是否对我们可以使用的模式有更多建议。 Note that the code below is for design and illustration purposes (c++ pseudocode) so is not meant to compile. 请注意,下面的代码仅用于设计和说明目的(c ++伪代码),因此不打算编译。

Approach 1 - Package users dependency inject data access classes to our package. 方法1-程序包用户依赖性将数据访问类注入到我们的程序包中。 We access these classes using run time type inference. 我们使用运行时类型推断来访问这些类。

// Define an interface class for all data accessor classes
class DataAccessor
{

}

// Some random data
class FooData
{
    int foo;
    int bar;
}

// A concrete data accessor
class FooDataAccessor : public DataAccessor
{
public:
    FooData getFooData()
    {
        FooData fooDataCopy;
        {
            //Locks cachedFooData in this scope
            ScopedCriticalSection _(dataCriticalSection);
            fooDataCopy.foo = cachedFooData.foo;
            fooDataCopy.bar = cachedFooData.bar;
        }
        return fooDataCopy;
    }
    void setFooData(FooData& fooData)
    {
        //Locks cachedFooData in this scope
        ScopedCriticalSection _(dataCriticalSection);
        cachedFooData.foo = dooData.foo;
        cachedFooData.bar = dooData.bar;
    }

private:
    FooData cachedFooData;
    CriticalSection dataCriticalSection; //Use this for locking cachedFooData to set the data.
}

class OurPackage
{
    OurPackage(std::vector<DataAccessor*>); //constructor which is injected the data accessors so that our package customers can own their lifecycle.
}

// How package users would inject the data accessors into our package, then later access the data
int main()
{
    std::vector<DataAccessor*> dataAccessors;
    //The package customer now populates the data Accessors with the instances they need.
    dataAccessors.push_back(new FooDataAccessor());        

    auto package = new OurPackage(dataAccessors);

    // How package users access the data, assume FooDataAccessor was at the front
    auto fooAccessor = dataAccessors.front();
    if (fooAccessor)
    {
        FooData data = fooAccessor->getFooData();
    }
}

// In OurPackage, set the actual data in caches
for (DataAccessor* dataAccessor : dataAccessors)
{
    //Use RTTI to find the instance we want
    if (auto dataAccessorTypeWeWant = dynamic_cast<DataAccessorTypeWeWant*>(dataAccessor) != nullptr)
    {
        //Set the data on dataAccessorTypeWeWant 
        //For example, set FooData
        FooData fooData;
        fooData.foo = 1;
        fooData.bar = 2;
        dataAccessorTypeWeWant.setFooData(fooData);
        break;
    }
}

2 - Use a singleton pattern instead 2- 改用单例模式

If the data access caches are singletons instead, package users don't have to manage the lifecycle of these classes and don't need to worry about passing pointers to instances of the data access caches around their application. 如果数据访问缓存是单例的,则包用户不必管理这些类的生命周期,也不必担心将指针传递到应用程序周围的数据访问缓存的实例。 This has all the pitfalls of singletons though. 虽然这有单身人士的所有陷阱。

Whatever patter that you chose you should use an Atomic Type that is founded into the library <atomic> , functionality available since C++11. 无论选择哪种模式,都应使用Aatomic Type,该原子类型已建立在库<atomic> ,该功能自C ++ 11起可用。 Whit this type you can create threadsafe variables, for example: 如果您使用这种类型,则可以创建线程安全变量,例如:

// Some random data
class FooData
{
    std::atomic<int>  foo;
    std::atomic<int>  bar;
}

I share you a description of this library from CPlusPlus : 我向您分享了CPlusPlus对该库的描述:

Atomic types are types that encapsulate a value whose access is guaranteed to not cause data races and can be used to synchronize memory accesses among different threads. 原子类型是封装一个值的类型,该值的访问保证不会引起数据争用,并且可用于在不同线程之间同步内存访问。

This is a sample. 这是一个示例。 If copy by value is what you need. 如果需要按值复制。

#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>

// Undefine to see data corruptions
#define USING_LOCK

std::atomic<int> atomic_i;

class FooData {
public:
    FooData() :foo(atomic_i.fetch_add(1, std::memory_order_relaxed)), bar(foo) {}
    ~FooData() { if (foo != bar) { std::cout << "Data corrupted!\n"; } }
private:
    int foo;
    int bar;
};

class FooDataAccessor {
public:
    FooData getFooData() {
#ifdef USING_LOCK
        std::lock_guard<std::mutex> l(_lock);
#endif // USING_LOCK
        return cachedFooData;
    }
    void setFooData(const FooData& fooData) {
#ifdef USING_LOCK
        std::lock_guard<std::mutex> l(_lock);
#endif // USING_LOCK
        cachedFooData = fooData;
    }

private:
    FooData cachedFooData;
#ifdef USING_LOCK
    std::mutex _lock;
#endif // USING_LOCK
};

void f(FooDataAccessor* accessor) {
    for (size_t i = 0; i < 1000; i++) {
        accessor->getFooData();
        accessor->setFooData(FooData());
    }
}

int main() {
    FooDataAccessor accessor;
    std::vector<std::thread> v;
    for (size_t i = 0; i < 5; i++) {
        v.emplace_back(f, &accessor);
    }
    for (auto& t : v) {
        t.join();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM