C ++ 11折返级锁策略 [英] C++11 reentrant class locking strategy

查看:167
本文介绍了C ++ 11折返级锁策略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须使用平普尔成语的接口,不过该接口必须折返。调用线程不需要知道锁定的,但是。这是第四部分的问题,一部分无偿做作C ++ 11的例子(例如包括解决几个常见问题,这样的问题,我已经重新各地运行:锁定平普尔右值和C ++ 11,其中的答案在他们的质量有所怀疑)。

I have an interface using the pimpl idiom, however the interface needs to be reentrant. Calling threads do not need to be aware of the locking, however. This is four part question and one part gratuitously contrived C++11 example (example included to address several FAQ-like questions that I've run across re: locking, pimpl, rvalue and C++11 where the answers were somewhat dubious in their quality).

在头,example.hpp:

In the header, example.hpp:

#ifndef EXAMPLE_HPP
#define EXAMPLE_HPP

#include <memory>
#include <string>

#ifndef BOOST_THREAD_SHARED_MUTEX_HPP
# include <boost/thread/shared_mutex.hpp>
#endif

namespace stackoverflow {

class Example final {
public:
  typedef ::boost::shared_mutex shared_mtx_t;
  typedef ::boost::shared_lock< shared_mtx_t > shared_lock_t;
  typedef ::boost::unique_lock< shared_mtx_t > unique_lock_t;

  Example();
  Example(const std::string& initial_foo);

  ~Example();
  Example(const Example&) = delete;             // Prevent copying
  Example& operator=(const Example&) = delete;  // Prevent assignment

  // Example getter method that supports rvalues
  std::string foo() const;

  // Example setter method using perfect forwarding & move semantics. Anything
  // that's std::string-like will work as a parameter.
  template<typename T>
  bool foo_set(T&& new_val);

  // Begin foo_set() variants required to deal with C types (e.g. char[],
  // char*). The rest of the foo_set() methods here are *NOT* required under
  // normal circumstances.

  // Setup a specialization for const char[] that simply forwards along a
  // std::string. This is preferred over having to explicitly instantiate a
  // bunch of const char[N] templates or possibly std::decay a char[] to a
  // char* (i.e. using a std::string as a container is a Good Thing(tm)).
  //
  // Also, without this, it is required to explicitly instantiate the required
  // variants of const char[N] someplace. For example, in example.cpp:
  //
  // template bool Example::foo_set<const char(&)[6]>(char const (&)[6]);
  // template bool Example::foo_set<const char(&)[7]>(char const (&)[7]);
  // template bool Example::foo_set<const char(&)[8]>(char const (&)[8]);
  // ...
  //
  // Eww. Best to just forward to wrap new_val in a std::string and proxy
  // along the call to foo_set<std::string>().
  template<std::size_t N>
  bool foo_set(const char (&new_val)[N]) { return foo_set(std::string(new_val, N)); }

  // Inline function overloads to support null terminated char* && const
  // char* arguments. If there's a way to reduce this duplication with
  // templates, I'm all ears because I wasn't able to generate a templated
  // versions that didn't conflict with foo_set<T&&>().
  bool foo_set(char* new_val)       { return foo_set(std::string(new_val)); }
  bool foo_set(const char* new_val) { return foo_set(std::string(new_val)); }

  // End of the foo_set() overloads.

  // Example getter method for a POD data type
  bool bar(const std::size_t len, char* dst) const;
  std::size_t bar_capacity() const;

  // Example setter that uses a unique lock to access foo()
  bool bar_set(const std::size_t len, const char* src);

  // Question #1: I can't find any harm in making Impl public because the
  // definition is opaque. Making Impl public, however, greatly helps with
  // implementing Example, which does have access to Example::Impl's
  // interface. This is also preferre, IMO, over using friend.
  class Impl;

private:
  mutable shared_mtx_t rw_mtx_;
  std::unique_ptr<Impl> impl_;
};

} // namespace stackoverflow

#endif // EXAMPLE_HPP

然后在执行

#include "example.hpp"

#include <algorithm>
#include <cstring>
#include <utility>

namespace stackoverflow {

class Example;
class Example::Impl;


#if !defined(_MSC_VER) || _MSC_VER > 1600
// Congratulations!, you're using a compiler that isn't broken

// Explicitly instantiate std::string variants
template bool Example::foo_set<std::string>(std::string&& src);
template bool Example::foo_set<std::string&>(std::string& src);
template bool Example::foo_set<const std::string&>(const std::string& src);

// The following isn't required because of the array Example::foo_set()
// specialization, but I'm leaving it here for reference.
//
// template bool Example::foo_set<const char(&)[7]>(char const (&)[7]);
#else
// MSVC workaround: msvc_rage_hate() isn't ever called, but use it to
// instantiate all of the required templates.
namespace {
  void msvc_rage_hate() {
    Example e;
    const std::string a_const_str("a");
    std::string a_str("b");
    e.foo_set(a_const_str);
    e.foo_set(a_str);
    e.foo_set("c");
    e.foo_set(std::string("d"));
  }
} // anon namespace
#endif // _MSC_VER



// Example Private Implementation

class Example::Impl final {
public:
  // ctors && obj boilerplate
  Impl();
  Impl(const std::string& init_foo);
  ~Impl() = default;
  Impl(const Impl&) = delete;
  Impl& operator=(const Impl&) = delete;

  // Use a template because we don't care which Lockable concept or LockType
  // is being used, just so long as a lock is held.
  template <typename LockType>
  bool bar(LockType& lk, std::size_t len, char* dst) const;

  template <typename LockType>
  std::size_t bar_capacity(LockType& lk) const;

  // bar_set() requires a unique lock
  bool bar_set(unique_lock_t& lk, const std::size_t len, const char* src);

  template <typename LockType>
  std::string foo(LockType& lk) const;

  template <typename T>
  bool foo_set(unique_lock_t& lk, T&& src);

private:
  // Example datatype that supports rvalue references
  std::string foo_;

  // Example POD datatype that doesn't support rvalue
  static const std::size_t bar_capacity_ = 16;
  char bar_[bar_capacity_ + 1];
};

// Example delegating ctor
Example::Impl::Impl() : Impl("default foo value") {}

Example::Impl::Impl(const std::string& init_foo) : foo_{init_foo} {
  std::memset(bar_, 99 /* ASCII 'c' */, bar_capacity_);
  bar_[bar_capacity_] = '\0'; // null padding
}


template <typename LockType>
bool
Example::Impl::bar(LockType& lk, const std::size_t len, char* dst) const {
  BOOST_ASSERT(lk.owns_lock());
  if (len != bar_capacity(lk))
    return false;
  std::memcpy(dst, bar_, len);

  return true;
}


template <typename LockType>
std::size_t
Example::Impl::bar_capacity(LockType& lk) const {
  BOOST_ASSERT(lk.owns_lock());
  return Impl::bar_capacity_;
}


bool
Example::Impl::bar_set(unique_lock_t &lk, const std::size_t len, const char* src) {
  BOOST_ASSERT(lk.owns_lock());

  // Return false if len is bigger than bar_capacity or the values are
  // identical
  if (len > bar_capacity(lk) || foo(lk) == src)
    return false;

  // Copy src to bar_, a side effect of updating foo_ if they're different
  std::memcpy(bar_, src, std::min(len, bar_capacity(lk)));
  foo_set(lk, std::string(src, len));
  return true;
}


template <typename LockType>
std::string
Example::Impl::foo(LockType& lk) const {
  BOOST_ASSERT(lk.owns_lock());
  return foo_;
}


template <typename T>
bool
Example::Impl::foo_set(unique_lock_t &lk, T&& src) {
  BOOST_ASSERT(lk.owns_lock());
  if (foo_ == src) return false;
  foo_ = std::move(src);
  return true;
}


// Example Public Interface

Example::Example() : impl_(new Impl{}) {}
Example::Example(const std::string& init_foo) : impl_(new Impl{init_foo}) {}
Example::~Example() = default;

bool
Example::bar(const std::size_t len, char* dst) const {
  shared_lock_t lk(rw_mtx_);
  return impl_->bar(lk, len , dst);
}

std::size_t
Example::bar_capacity() const {
  shared_lock_t lk(rw_mtx_);
  return impl_->bar_capacity(lk);
}

bool
Example::bar_set(const std::size_t len, const char* src) {
  unique_lock_t lk(rw_mtx_);
  return impl_->bar_set(lk, len, src);
}

std::string
Example::foo() const {
  shared_lock_t lk(rw_mtx_);
  return impl_->foo(lk);
}

template<typename T>
bool
Example::foo_set(T&& src) {
  unique_lock_t lk(rw_mtx_);
  return impl_->foo_set(lk, std::forward<T>(src));
}

} // namespace stackoverflow

和我的问题是:


  1. 有没有更好的方式来处理私人实施内部锁定?

  2. 有没有在做默认地将Impl公众给出的定义是不透明的?任何实际损害

  3. 当使用铿锵的 -O4 启用链结时间优化,它应该是可能的连接器绕过的std ::的unique_ptr 的解除引用开销。有没有人证实?

  4. 有没有办法来调用 foo_set(ASDF)并有正确的例子链接?我们遇到找出正确的显式模板实例化是什么问题为const char [6] 。现在我设置,创建一个的std ::字符串对象和代理调用foo_set()一个模板专业化。所有的事情考虑,这似乎是最好的前进方式,但我想知道如何通过ASDF和的std ::衰减的结果。

  1. Is there a better way to handle locking inside of the private implementation?
  2. Is there any actual harm in making Impl public given the definition is opaque?
  3. When using clang's -O4 to enable Link-Time Optimization, it should be possible for the linker to bypass the dereference overhead of std::unique_ptr. Has anyone verified that?
  4. Is there a way to call foo_set("asdf") and have the example link correctly? We're having problems figuring out what the correct explicit template instantiation is for const char[6]. For now I've setup a template specialization that creates a std::string object and proxies a call to foo_set(). All things considered, this seems like the best way forward, but I would like to know how to pass "asdf" and std::decay the result.

关于锁定策略,我已经开发了对这一明显的偏差有以下几个原因:

Regarding the locking strategy, I've developed an obvious bias towards this for several reasons:


  • 我可以改变了互斥体是一个独特的互斥量适当

  • 将设计默认地将Impl API,包括所需的锁,有锁的语义很强的编译时间保证

  • 这是很难忘记的东西锁住(和一个简单的API的错误,当发生这种情况,再次编译器将捕获这个曾经的API已经被固定)

  • 这是很难留下点什么锁定或创建一个死锁由于RAII和具有默认地将Impl没有互斥
  • 参考
  • 的模板使用,从而不再需要从一个独特的锁定降级到共享锁定

  • 由于这种锁策略占地code比实际需要,它需要明确的努力,降级,从独特的锁共享,处理与共享锁需要做的一切太常见scenarior,其中假设在进入一个独特的锁定区域复检

  • Bug修复或默认地将Impl API更改不需要重新编译整个应用程序,因为example.hpp的API外部固定的。

我读过 ACE 使用这种类型的锁定策略为好了,但我欢迎ACE用户或其他一些真实世界的批评回复:各地传递锁作为接口的必要组成部分。

I've read that ACE uses this type of locking strategy as well, but I'm welcome some realworld criticism from ACE users or others re: passing the lock around as a required part of the interface.

为了完整起见,这里是一个example_main.cpp乡亲来啃。

For the sake of completeness, here's an example_main.cpp for folks to chew on.

#include <sysexits.h>

#include <cassert>
#include <iostream>
#include <memory>
#include <stdexcept>

#include "example.hpp"

int
main(const int /*argc*/, const char** /*argv*/) {
  using std::cout;
  using std::endl;
  using stackoverflow::Example;

  {
    Example e;
    cout << "Example's foo w/ empty ctor arg: " << e.foo() << endl;
  }

  {
    Example e("foo");
    cout << "Example's foo w/ ctor arg: " << e.foo() << endl;
  }

  try {
    Example e;
    { // Test assignment from std::string
      std::string str("cccccccc");
      e.foo_set(str);
      assert(e.foo() == "cccccccc");  // Value is the same
      assert(str.empty());            // Stole the contents of a_str
    }
    { // Test assignment from a const std::string
      const std::string const_str("bbbbbbb");
      e.foo_set(const_str);
      assert(const_str == "bbbbbbb");               // Value is the same
      assert(const_str.c_str() != e.foo().c_str()); // Made a copy
    }
    {
      // Test a const char[7] and a temporary std::string
      e.foo_set("foobar");
      e.foo_set(std::string("ddddd"));
    }
    { // Test char[7]
      char buf[7] = {"foobar"};
      e.foo_set(buf);
      assert(e.foo() == "foobar");
    }
    { //// And a *char[] & const *char[]
      // Use unique_ptr to automatically free buf
      std::unique_ptr<char[]> buf(new char[7]);
      std::memcpy(buf.get(), "foobar", 6);
      buf[6] = '\0';
      e.foo_set(buf.get());
      const char* const_ptr = buf.get();
      e.foo_set(const_ptr);
      assert(e.foo() == "foobar");
    }

    cout << "Example's bar capacity: " << e.bar_capacity() << endl;
    const std::size_t len = e.bar_capacity();

    std::unique_ptr<char[]> buf(new char[len +1]);

    // Copy bar in to buf
    if (!e.bar(len, buf.get()))
      throw std::runtime_error("Unable to get bar");
    buf[len] = '\0'; // Null terminate the C string
    cout << endl << "foo and bar (a.k.a.) have different values:" << endl;
    cout << "Example's foo value: " << e.foo() << endl;
    cout << "Example's bar value: " << buf.get() << endl;

    // Set bar, which has a side effect of calling foo_set()
    buf[0] = 'c'; buf[1] = buf[2] = '+'; buf[3] = '\0';
    if (!e.bar_set(sizeof("c++") - 1, buf.get()))
      throw std::runtime_error("Unable to set bar");

    cout << endl << "foo and bar now have identical values but only one lock was acquired when setting:" << endl;
    cout << "Example's foo value: " << e.foo() << endl;
    cout << "Example's bar value: " << buf.get() << endl;
  } catch (...) {
    return EX_SOFTWARE;
  }

  return EX_OK;
}

和构建说明使用 C ++ 11 的libc ++

clang++ -O4 -std=c++11 -stdlib=libc++ -I/path/to/boost/include -o example.cpp.o -c example.cpp
clang++ -O4 -std=c++11 -stdlib=libc++ -I/path/to/boost/include -o example_main.cpp.o -c example_main.cpp
clang++ -O4 -stdlib=libc++ -o example example.cpp.o example_main.cpp.o /path/to/boost/lib/libboost_exception-mt.dylib /path/to/boost/lib/libboost_system-mt.dylib /path/to/boost/lib/libboost_thread-mt.dylib


作为一个小的奖金,我更新了这个例子,包括使用在 foo_set()方法右值引用完美转发。虽然并不完美,它需要较长的时间比我预期得到模板实例正确连接时,这是一个问题。这也包括对C基本类型适当的重载,其中包括:的char * 为const char * 的char [N] 为const char [N]


As a small bonus, I updated this example to include perfect forwarding using rvalue references in the foo_set() method. While not perfect, it took longer than I anticipated to get the template instantiation correct which is an issue when linking. This also includes the appropriate overloads for C basic types, including: char*, const char*, char[N], and const char[N].

推荐答案

使用PIMPL方法,互斥量应该是执行工作的一部分。这将让你来当锁开始掌握。

Using a Pimpl idiom, the mutex should be part of the implementation. This will let you to master when the lock is started.

顺便说一句,为什么使用unique_lock当lock_guard就足够了?

BTW, why using a unique_lock when a lock_guard will be enough?

我看不到任何优势,使公众implement执行。

I don't see any advantage to make impl public.

的std ::的unique_ptr应该像大多数现代人的编译器的指针一样高效。然而,未验证。

std::unique_ptr should be as efficient as a pointer for most of the moderns compilers. Not verified however.

我会转发为const char [N] foo_set不是

I would forward the const char[N] foo_set not as

  template<std::size_t N>
  bool foo_set(const char (&new_val)[N]) { return foo_set(std::string(new_val, N)); }

但像

  template<std::size_t N>
  bool foo_set(const char (&new_val)[N]) { return foo_set(N, new_val); }

这避免了头文件中的字符串创建,让需要做任何的实现。

This avoids the string creation on the header file and let the implementation do whatever is needed.

这篇关于C ++ 11折返级锁策略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆