Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are mutexes and condition variables trivially copyable?

LWG 2424 discusses the undesirable status of atomics, mutexes and condition variables as trivially copyable in C++14. I appreciate that a fix is already lined up, but std::mutex, std::condition variable et al. appear to have non-trivial destructors. For example:

30.4.1.2.1 Class mutex [thread.mutex.class]

namespace std {
  class mutex {
  public:
    constexpr mutex() noexcept;
    ~mutex(); // user-provided => non-trivial

    …
  }
}

Shouldn't this disqualify them as trivially copyable?

like image 450
Joseph Thomson Avatar asked Mar 22 '16 16:03

Joseph Thomson


People also ask

Is a mutex the same thing as a conditional variable?

Since we are using a kernel supported mutex, we can safely presume that each thread will be blocked when the other thread needs to access the critical section of code. is that you refer to e.g. a pthread conditional variable. But to me a mutex and a conditional variable is not the same thing and have different usage.

What is the difference between mutex () and recursive_mutex ()?

mutex: provides the core functions lock () and unlock () and the non-blocking try_lock () method that returns if the mutex is not available. recursive_mutex: allows multiple acquisitions of the mutex from the same thread.

What is the difference between mutex and lock in C++?

For this, I used a mutex and a lock on the mutex. A mutex is a core synchronization primitive and in C++11, it comes in four flavors in the <mutex> header. mutex: provides the core functions lock () and unlock () and the non-blocking try_lock () method that returns if the mutex is not available.

What happens if mutex is used during test-and-set?

This could lead to extremely unpleasant happenings. Using a mutex is supposed to prevent this problem by insuring that the executing thread is NOT swapped out during the internal test-and-set operation. It does this by running the test-and-set at kernel level and by making sure that the scheduler isn't run until the operation has completed.


2 Answers

Either it was my mistake, or I was misquoted, and I honestly don't recall which.

However, I have this very strongly held advice on the subject:

Do not use is_trivial nor is_trivially_copyable! EVER!!!

Instead use one of these:

is_trivially_destructible<T>
is_trivially_default_constructible<T>
is_trivially_copy_constructible<T>
is_trivially_copy_assignable<T>
is_trivially_move_constructible<T>
is_trivially_move_assignable<T>

Rationale:

tldr: See this excellent question and correct answer.

No one (including myself) can remember the definition of is_trivial and is_trivially_copyable. And if you do happen to look it up, and then spend 10 minutes analyzing it, it may or may not do what you intuitively think it does. And if you manage to analyze it correctly, the CWG may well change its definition with little or no notice and invalidate your code.

Using is_trivial and is_trivially_copyable is playing with fire.

However these:

is_trivially_destructible<T>
is_trivially_default_constructible<T>
is_trivially_copy_constructible<T>
is_trivially_copy_assignable<T>
is_trivially_move_constructible<T>
is_trivially_move_assignable<T>

do exactly what they sound like they do, and are not likely to ever have their definition changed. It may seem overly verbose to have to deal with each of the special members individually. But it will pay off in the stability/reliability of your code. And if you must, package these individual traits up into a custom trait.

Update

For example, clang & gcc compile this program:

#include <type_traits>

template <class T>
void
test()
{
    using namespace std;
    static_assert(!is_trivial<T>{}, "");
    static_assert( is_trivially_copyable<T>{}, "");
    static_assert( is_trivially_destructible<T>{}, "");
    static_assert( is_destructible<T>{}, "");
    static_assert(!is_trivially_default_constructible<T>{}, "");
    static_assert(!is_trivially_copy_constructible<T>{}, "");
    static_assert( is_trivially_copy_assignable<T>{}, "");
    static_assert(!is_trivially_move_constructible<T>{}, "");
    static_assert( is_trivially_move_assignable<T>{}, "");
}

struct X
{
    X(const X&) = delete;
};

int
main()
{
    test<X>();
}

Note that X is trivially copyable, but not trivially copy constructible. To the best of my knowledge, this is conforming behavior.

VS-2015 currently says that X is neither trivially copyable nor trivially copy constructible. I believe this is wrong according to the current spec, but it sure matches what my common sense tells me.

If I needed to memcpy to uninitialized memory, I would trust is_trivially_copy_constructible over is_trivially_copyable to assure me that such an operation would be ok. If I wanted to memcpy to initialized memory, I would check is_trivially_copy_assignable.

like image 53
Howard Hinnant Avatar answered Oct 22 '22 19:10

Howard Hinnant


Not all implementations provide a nontrivial destructor for mutex. See libstdc++ (and assume that __GTHREAD_MUTEX_INIT has been defined):

  // Common base class for std::mutex and std::timed_mutex
  class __mutex_base
  {
  // […]
#ifdef __GTHREAD_MUTEX_INIT
    __native_type  _M_mutex = __GTHREAD_MUTEX_INIT;

    constexpr __mutex_base() noexcept = default;
#else
  // […]

    ~__mutex_base() noexcept { __gthread_mutex_destroy(&_M_mutex); }
#endif
  // […]
  };

  /// The standard mutex type.
  class mutex : private __mutex_base
  {
    // […]
    mutex() noexcept = default;
    ~mutex() = default;

    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;
  };

This implementation of mutex is both standard conforming and trivially copyable (which can be verified via Coliru). Similarly, nothing stops an implementation from keeping condition_variable trivially destructible (cf. [thread.condition.condvar]/6, although I couldn't find an implementation that does).

The bottom line is that we need clear, normative guarantees, and not clever, subtle interpretations of what condition_variable does or doesn't have to do (and how it has to accomplish that).

like image 28
Columbo Avatar answered Oct 22 '22 20:10

Columbo