Skip to content

Instantly share code, notes, and snippets.

@rioki
Last active May 9, 2024 12:58
Show Gist options
  • Save rioki/e225f21e39879fec37f32e9c2cd1365b to your computer and use it in GitHub Desktop.
Save rioki/e225f21e39879fec37f32e9c2cd1365b to your computer and use it in GitHub Desktop.
sthread, like jthread, just simpler.
// latch
// Copyright 2017-2024 Sean Farrell <sean.farrell@rioki.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
#pragma once
#include <cstddef>
#include <mutex>
#include <condition_variable>
namespace stdex
{
class latch
{
public:
[[nodiscard]] static std::ptrdiff_t max() noexcept
{
return std::numeric_limits<std::ptrdiff_t>::max();
}
explicit latch(std::ptrdiff_t expected) noexcept
: count(expected) {}
~latch() = default;
void count_down(std::ptrdiff_t n = 1) noexcept
{
auto lock = std::unique_lock<std::mutex>{mutex};
count -= n;
if (count <= 0)
{
cond.notify_all();
}
}
void wait() const
{
auto lock = std::unique_lock<std::mutex>{mutex};
cond.wait(lock, [&]{return count <= 0;});
}
private:
std::ptrdiff_t count;
mutable std::mutex mutex;
mutable std::condition_variable cond;
latch(const latch&) = delete;
latch& operator = (const latch&) = delete;
};
}
// latch
// Copyright 2017-2024 Sean Farrell <sean.farrell@rioki.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
#include "latch.h"
#include <gtest/gtest.h>
#include <atomic>
#include <future>
using namespace std::chrono_literals;
TEST(latch, wait)
{
auto my_latch = stdex::latch{12u};
auto count = std::atomic<unsigned int>{0u};
std::vector<std::future<void>> futures;
for (auto i = 0u; i < 12; i++)
{
auto f = std::async(std::launch::async, [&] () {
count++;
my_latch.count_down();
});
futures.push_back(std::move(f));
}
my_latch.wait();
EXPECT_EQ(12u, count);
for (auto& f : futures)
{
f.get();
}
}
// sthread
// Copyright 2024 Sean Farrell <sean.farrell@rioki.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
#pragma once
#include <thread>
#include <atomic>
#include <memory>
namespace stdex
{
class stop_token
{
public:
stop_token() noexcept
: state(std::make_shared<std::atomic<bool>>(false)) {}
stop_token(const stop_token&) noexcept = default;
stop_token(stop_token&&) noexcept = default;
~stop_token() = default;
stop_token& operator = (const stop_token&) noexcept = default;
stop_token& operator = (stop_token&&) noexcept = default;
[[nodiscard]] bool stop_requested() const noexcept
{
return state->load();
}
[[nodiscard]] bool stop_possible() const noexcept
{
return !stop_requested();
}
bool request_stop() noexcept
{
auto prev_state = state->exchange(true);
return prev_state == false;
}
private:
std::shared_ptr<std::atomic<bool>> state;
};
class sthread
{
public:
using id = std::thread::id;
sthread() noexcept = default;
sthread(sthread&& other) noexcept = default;
template<class Function, class... Args>
explicit sthread(Function&& f, Args&&... args)
: impl(std::thread(std::forward<Function>(f), token, std::forward<Args>(args)...)) {}
~sthread()
{
if (joinable())
{
request_stop();
join();
}
}
sthread& operator = (sthread&& other) noexcept = default;
[[nodiscard]] id get_id() const noexcept
{
return impl.get_id();
}
[[nodiscard]] bool joinable() const noexcept
{
return impl.joinable();
}
void join()
{
impl.join();
}
[[nodiscard]] bool stop_possible() const noexcept
{
return joinable() && token.stop_possible();
}
bool request_stop() noexcept
{
return token.request_stop();
}
private:
stop_token token;
std::thread impl;
sthread(const sthread&) noexcept = delete;
sthread& operator = (const sthread& other) noexcept = delete;
};
}
// sthread
// Copyright 2024 Sean Farrell <sean.farrell@rioki.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
#include "sthread.h"
#include "latch.h"
#include <gtest/gtest.h>
TEST(sthread, create_and_destroy)
{
auto thread = stdex::sthread{};
EXPECT_FALSE(thread.joinable());
EXPECT_FALSE(thread.stop_possible());
}
TEST(sthread, launch_thread)
{
auto tid = stdex::sthread::id{};
auto thread = stdex::sthread{[&] (stdex::stop_token stoken) {
tid = std::this_thread::get_id();
}};
thread.join();
EXPECT_NE(tid, std::this_thread::get_id());
}
TEST(sthread, launch_thread_with_args)
{
auto a = 0u;
auto thread = stdex::sthread{[&] (stdex::stop_token stoken, unsigned int awnser) {
a = awnser;
}, 42u};
thread.join();
EXPECT_EQ(a, 42u);
}
TEST(sthread, id)
{
auto latch = stdex::latch{1};
auto tid = stdex::sthread::id{};
auto thread = stdex::sthread{[&] (stdex::stop_token stoken) {
tid = std::this_thread::get_id();
latch.count_down();
}};
latch.wait();
EXPECT_EQ(thread.get_id(), tid);
thread.join();
}
TEST(sthread, empty_id)
{
auto thread = stdex::sthread{};
EXPECT_EQ(thread.get_id(), stdex::sthread::id{});
}
TEST(sthread, move_assignment)
{
auto latch = stdex::latch{1};
auto outside = stdex::sthread{};
{
auto inside = stdex::sthread{[&] (stdex::stop_token token) {
latch.wait();
}};
outside = std::move(inside);
EXPECT_FALSE(inside.joinable());
}
EXPECT_TRUE(outside.joinable());
latch.count_down();
outside.join();
}
TEST(sthread, move_constructor)
{
auto latch = stdex::latch{1};
auto construct = [&] () {
auto inside = stdex::sthread{[&] (stdex::stop_token token) {
latch.wait();
}};
return inside;
};
auto outside = construct();
EXPECT_TRUE(outside.joinable());
latch.count_down();
outside.join();
}
TEST(sthread, request_stop)
{
auto thread = stdex::sthread{[] (stdex::stop_token token) {
while (!token.stop_requested())
{
std::this_thread::yield();
}
}};
thread.request_stop();
thread.join();
}
TEST(sthread, automatic_stop_request)
{
auto thread = stdex::sthread{[] (stdex::stop_token token) {
while (!token.stop_requested())
{
std::this_thread::yield();
}
}};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment