forked from andreasbuhr/cppcoro
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathspin_wait.cpp
More file actions
101 lines (90 loc) · 2.17 KB
/
Copy pathspin_wait.cpp
File metadata and controls
101 lines (90 loc) · 2.17 KB
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
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#include "spin_wait.hpp"
#include <cppcoro/config.hpp>
#include <thread>
#if CPPCORO_OS_WINNT
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
namespace
{
namespace local
{
constexpr std::uint32_t yield_threshold = 10;
}
}
namespace cppcoro
{
spin_wait::spin_wait() noexcept
{
reset();
}
bool spin_wait::next_spin_will_yield() const noexcept
{
return m_count >= local::yield_threshold;
}
void spin_wait::reset() noexcept
{
static const std::uint32_t initialCount =
std::thread::hardware_concurrency() > 1 ? 0 : local::yield_threshold;
m_count = initialCount;
}
void spin_wait::spin_one() noexcept
{
#if CPPCORO_OS_WINNT
// Spin strategy taken from .NET System.SpinWait class.
// I assume the Microsoft developers knew what they're doing.
if (!next_spin_will_yield())
{
// CPU-level pause
// Allow other hyper-threads to run while we busy-wait.
// Make each busy-spin exponentially longer
const std::uint32_t loopCount = 2u << m_count;
for (std::uint32_t i = 0; i < loopCount; ++i)
{
::YieldProcessor();
::YieldProcessor();
}
}
else
{
// We've already spun a number of iterations.
//
const auto yieldCount = m_count - local::yield_threshold;
if (yieldCount % 20 == 19)
{
// Yield remainder of time slice to another thread and
// don't schedule this thread for a little while.
::SleepEx(1, FALSE);
}
else if (yieldCount % 5 == 4)
{
// Yield remainder of time slice to another thread
// that is ready to run (possibly from another processor?).
::SleepEx(0, FALSE);
}
else
{
// Yield to another thread that is ready to run on the
// current processor.
::SwitchToThread();
}
}
#else
if (next_spin_will_yield())
{
std::this_thread::yield();
}
#endif
++m_count;
if (m_count == 0)
{
// Don't wrap around to zero as this would go back to
// busy-waiting.
m_count = local::yield_threshold;
}
}
}