Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/cn/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ struct MultipleThreading

对于 `SingleThreading` 而言,所有的 `Mutex` 、 `Atomic` 和 `ConditionVariable` 类型都是不会起任何作用的假类型。

对于多线程而言,默认的 `Mutex` 是 `std::mutex` 。 eventpp 也提供了一个使用自旋锁作为互斥量的 `SpinLock` 类。
对于多线程而言,默认的 `Mutex` 是 `std::mutex` 。 eventpp 也提供了一个使用自旋锁作为互斥量的 `SpinLock` 类。 `SpinLock` 提供 `lock` 、 `try_lock` 和 `unlock` ,因此可以与 `std::lock_guard` 和 `std::unique_lock` 一起使用。等待的线程在自旋一段时间后会让出 CPU ,所以在锁被长时间持有时不会空耗 CPU 。

当只有较少的线程时(和 CPU 核心数差不多的线程数),`eventpp::SpinLock` 的性能比 `std::mutex` 更高一些。当线程数超过 CPU 核心数时, `eventpp::SpinLock` 的性能弱于 `std::mutex` 。

Expand Down
2 changes: 1 addition & 1 deletion doc/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ struct MultipleThreading
```
For `SingleThreading`, all the types `Mutex`, `Atomic`, and `ConditionVariable` are dummy types that don't do anything.

For multiple threading, the default `Mutex` is `std::mutex`. `eventpp` also provides a `SpinLock` class which uses spinlock as the mutex.
For multiple threading, the default `Mutex` is `std::mutex`. `eventpp` also provides a `SpinLock` class which uses spinlock as the mutex. `SpinLock` has `lock`, `try_lock` and `unlock`, so it can be used with `std::lock_guard` and `std::unique_lock`. The waiting thread yields after spinning for a while, so it doesn't burn the CPU when the lock is held for long.
When there are fewer threads (about around the number of CPU cores), `eventpp::SpinLock` has better performance than `std::mutex`. When there are much more threads than CPU cores, `eventpp::SpinLock` has worse performance than `std::mutex`.
Please [read the benchmark](benchmark.md) for benchmark data.

Expand Down
25 changes: 21 additions & 4 deletions include/eventpp/eventpolicies.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <atomic>
#include <condition_variable>
#include <thread>
#include <map>
#include <unordered_map>
#include <list>
Expand All @@ -34,20 +35,36 @@ struct TagHeterCallbackList : public TagHeter {};
struct TagHeterEventDispatcher : public TagHeter {};
struct TagHeterEventQueue : public TagHeter {};

// A simple spin lock. The waiting thread spins on a plain load (test and test-and-set), so it doesn't keep
// invalidating the cache line of the lock, and it yields after spinning for a while, so it doesn't burn
// the CPU when the lock is held for long or when there are more threads than CPU cores.
struct SpinLock
{
public:
void lock() {
while(locked.test_and_set(std::memory_order_acquire)) {
while(locked.exchange(true, std::memory_order_acquire)) {
int spinCount = 0;
while(locked.load(std::memory_order_relaxed)) {
if(++spinCount >= spinCountBeforeYield) {
spinCount = 0;
std::this_thread::yield();
}
}
}
}

bool try_lock() {
return ! locked.exchange(true, std::memory_order_acquire);
}

void unlock() {
locked.clear(std::memory_order_release);
locked.store(false, std::memory_order_release);
}

private:
std::atomic_flag locked = ATOMIC_FLAG_INIT;
enum { spinCountBeforeYield = 100 };

std::atomic<bool> locked { false };
};

template <
Expand Down
1 change: 1 addition & 0 deletions tests/unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ set(SRC_TEST
test_conditionalfunctor.cpp
test_anyid.cpp
test_anydata.cpp
test_spinlock.cpp
)

add_executable(
Expand Down
96 changes: 96 additions & 0 deletions tests/unittest/test_spinlock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "test.h"
#include "eventpp/eventpolicies.h"
#include "eventpp/callbacklist.h"

#include <thread>
#include <mutex>
#include <vector>

TEST_CASE("SpinLock, lock, try_lock, unlock")
{
eventpp::SpinLock spinLock;

REQUIRE(spinLock.try_lock());
REQUIRE(! spinLock.try_lock());
spinLock.unlock();
REQUIRE(spinLock.try_lock());
spinLock.unlock();

spinLock.lock();
REQUIRE(! spinLock.try_lock());
spinLock.unlock();

{
std::lock_guard<eventpp::SpinLock> lockGuard(spinLock);
REQUIRE(! spinLock.try_lock());
}
REQUIRE(spinLock.try_lock());
spinLock.unlock();
}

TEST_CASE("SpinLock, multi threading")
{
eventpp::SpinLock spinLock;

// more threads than cores to exercise the yield
constexpr int threadCount = 64;
constexpr int incrementCountPerThread = 1024 * 16;

int counter = 0;

std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([&spinLock, &counter, incrementCountPerThread]() {
for(int k = 0; k < incrementCountPerThread; ++k) {
std::lock_guard<eventpp::SpinLock> lockGuard(spinLock);
++counter;
}
});
}
for(int i = 0; i < threadCount; ++i) {
threadList[i].join();
}

REQUIRE(counter == threadCount * incrementCountPerThread);
}

TEST_CASE("CallbackList, SpinLock, multi threading")
{
using CL = eventpp::CallbackList<void (int), eventpp::GeneralThreading<eventpp::SpinLock> >;
CL callbackList;

constexpr int threadCount = 16;
constexpr int callbackCountPerThread = 1024;

std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([&callbackList, callbackCountPerThread]() {
for(int k = 0; k < callbackCountPerThread; ++k) {
callbackList.append([](int) {});
}
});
}
for(int i = 0; i < threadCount; ++i) {
threadList[i].join();
}

int count = 0;
callbackList.forEach([&count](const CL::Callback &) {
++count;
});
REQUIRE(count == threadCount * callbackCountPerThread);
}