File: spin_lock_mutex.h

package info (click to toggle)
opentelemetry-cpp 1.23.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,368 kB
  • sloc: cpp: 96,239; sh: 1,766; makefile: 38; python: 31
file content (133 lines) | stat: -rw-r--r-- 3,969 bytes parent folder | download | duplicates (16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <atomic>
#include <chrono>
#include <thread>

#include "opentelemetry/version.h"

#if defined(_MSC_VER)
#  define _WINSOCKAPI_  // stops including winsock.h
#  include <windows.h>
#elif defined(__i386__) || defined(__x86_64__)
#  if defined(__clang__)
#    include <emmintrin.h>
#  elif defined(__INTEL_COMPILER)
#    include <immintrin.h>
#  endif
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace common
{

constexpr int SPINLOCK_FAST_ITERATIONS = 100;
constexpr int SPINLOCK_SLEEP_MS        = 1;

/**
 * A Mutex which uses atomic flags and spin-locks instead of halting threads.
 *
 * This mutex uses an incremental back-off strategy with the following phases:
 * 1. A tight spin-lock loop (pending: using hardware PAUSE/YIELD instructions)
 * 2. A loop where the current thread yields control after checking the lock.
 * 3. Issuing a thread-sleep call before starting back in phase 1.
 *
 * This is meant to give a good balance of perofrmance and CPU consumption in
 * practice.
 *
 * This mutex uses an incremental back-off strategy with the following phases:
 * 1. A tight spin-lock loop (pending: using hardware PAUSE/YIELD instructions)
 * 2. A loop where the current thread yields control after checking the lock.
 * 3. Issuing a thread-sleep call before starting back in phase 1.
 *
 * This is meant to give a good balance of perofrmance and CPU consumption in
 * practice.
 *
 * This class implements the `BasicLockable` specification:
 * https://en.cppreference.com/w/cpp/named_req/BasicLockable
 */
class SpinLockMutex
{
public:
  SpinLockMutex() noexcept {}
  ~SpinLockMutex() noexcept                       = default;
  SpinLockMutex(const SpinLockMutex &)            = delete;
  SpinLockMutex &operator=(const SpinLockMutex &) = delete;

  static inline void fast_yield() noexcept
  {
// Issue a Pause/Yield instruction while spinning.
#if defined(_MSC_VER)
    YieldProcessor();
#elif defined(__i386__) || defined(__x86_64__)
#  if defined(__clang__) || defined(__INTEL_COMPILER)
    _mm_pause();
#  else
    __builtin_ia32_pause();
#  endif
#elif defined(__armel__) || defined(__ARMEL__)
    asm volatile("nop" ::: "memory");
#elif defined(__arm__) || defined(__aarch64__)  // arm big endian / arm64
    __asm__ __volatile__("yield" ::: "memory");
#else
    // TODO: Issue PAGE/YIELD on other architectures.
#endif
  }

  /**
   * Attempts to lock the mutex.  Return immediately with `true` (success) or `false` (failure).
   */
  bool try_lock() noexcept
  {
    return !flag_.load(std::memory_order_relaxed) &&
           !flag_.exchange(true, std::memory_order_acquire);
  }

  /**
   * Blocks until a lock can be obtained for the current thread.
   *
   * This mutex will spin the current CPU waiting for the lock to be available.  This can have
   * decent performance in scenarios where there is low lock contention and lock-holders achieve
   * their work quickly.  It degrades in scenarios where locked tasks take a long time.
   */
  void lock() noexcept
  {
    for (;;)
    {
      // Try once
      if (!flag_.exchange(true, std::memory_order_acquire))
      {
        return;
      }
      // Spin-Fast (goal ~10ns)
      for (std::size_t i = 0; i < SPINLOCK_FAST_ITERATIONS; ++i)
      {
        if (try_lock())
        {
          return;
        }
        fast_yield();
      }
      // Yield then try again (goal ~100ns)
      std::this_thread::yield();
      if (try_lock())
      {
        return;
      }
      // Sleep and then start the whole process again. (goal ~1000ns)
      std::this_thread::sleep_for(std::chrono::milliseconds(SPINLOCK_SLEEP_MS));
    }
    return;
  }
  /** Releases the lock held by the execution agent. Throws no exceptions. */
  void unlock() noexcept { flag_.store(false, std::memory_order_release); }

private:
  std::atomic<bool> flag_{false};
};

}  // namespace common
OPENTELEMETRY_END_NAMESPACE