/YACLib

Yet Another Concurrency Library

Primary LanguageC++MIT LicenseMIT

YACLib

Yet Another Concurrency Library

GitHub license FOSSA status

Test Test with Google sanitizer Check code format

Test coverage Codacy Badge

Discord

Table of Contents

About YACLib

YACLib is a lightweight C++ library for concurrent and parallel task execution, that is striving to satisfy the following properties:

  • Easy to use
  • Easy to build
  • Zero cost abstractions
  • Good test coverage

For more details check our design document and documentation.

Getting started

For quick start just paste this code in your CMakeLists.txt file.

include(FetchContent)
FetchContent_declare(yaclib
  GIT_REPOSITORY https://github.com/YACLib/YACLib.git
  GIT_TAG main
  )
FetchContent_makeAvailable(yaclib)
link_libraries(yaclib)

For more details check install guide.

Examples

Here are short examples of using some features from YACLib, for details check documentation.

Asynchronous pipeline

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
yaclib::Run(tp, [] {
    return 42;
  }).Then([](int r) {
    return r * 2;
  }).Then([](int r) {
    return r + 1; 
  }).Then([](int r) {
    return std::to_string(r);
  }).Subscribe([](std::string r) {
    std::cout << "Pipeline result: <"  << r << ">" << std::endl;
  });
};

Thread Pool

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
tp->Execute([] {
  // some computations...
});
tp->Execute([] {
  // some computations...
});

tp->Stop();
tp->Wait();

Serial Executor, Strand, Async Mutex

auto tp = yaclib::MakeThreadPool(4);
// decorated thread pool by serializing tasks:
auto strand = yaclib::MakeSerial(tp);

size_t counter = 0;
auto tp2 = yaclib::MakeThreadPool(4);

for (size_t i = 0; i < 100; ++i) {
  tp2->Execute([&] {
    strand->Execute([&] {
      ++counter; // no data race!
    });
  });
}

WhenAll

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
std::vector<yaclib::Future<int>> fs;

// Run parallel computations
for (size_t i = 0; i < 5; ++i) {
  fs.push_back(yaclib::Run(tp, [i]() -> int {
    return random() * i;
  }));
}

// Will be ready when all futures are ready
yaclib::Future<std::vector<int>> all = yaclib::WhenAll(fs.begin(), fs.size());
std::vector<int> unique_ints = std::move(all).Then([](std::vector<int> ints) {
  ints.erase(std::unique(ints.begin(), ints.end()), ints.end());
  return ints;
}).Get().Ok();

WhenAny

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
std::vector<yaclib::Future<int>> fs;

// Run parallel computations
for (size_t i = 0; i < 5; ++i) {
  fs.push_back(yaclib::Run(tp, [i] {
    // connect with one of the database shard
    return i;
  }));
}

// Will be ready when any future is ready
yaclib::WhenAny(fs.begin(), fs.size()).Subscribe([](int i) {
  // some work with database
});

Future unwrapping

Sometimes it is necessary to return from one async function the result of the other. It would be possible with the wait on this result. But this would cause blocking thread while waiting for the task to complete.

This problem can be solved using future unwrapping: when an async function returns a Future object, instead of setting its result to the Future object, the inner Future will "replace" the outer Future. This means that the outer Future will complete when the inner Future finishes and will acquire the result of the inner Future.

auto tp_output = yaclib::MakeThreadPool(/*threads=*/1);
auto tp_compute = yaclib::MakeThreadPool(/*threads=CPU cores*/);

auto future = yaclib::Run(tp_output, [] {
  std::cout << "Outer task" <<   std::endl;
  return yaclib::Run(tp_compute, [] { return 42; });
}).Then(/*tp_compute*/ [](int result) {
  result *= 13;
  return yaclib::Run(tp_output, [result] { 
    return std::cout << "Result = " << result << std::endl; 
  });
});

Timed wait

auto tp = yaclib:MakeThreadPool(/*threads=*/4);

yaclib::Future<int> f1 = yaclib::Run(tp, [] { return 42; });
yaclib::Future<double> f2 = yaclib::Run(tp, [] { return 15.0; });

yaclib::WaitFor(10ms, f1, f2);  // or yaclib::Wait / yaclib::WaitUntil

if (f1.Ready()) {
  Process(std::as_const(f1).Get());
  yaclib::util::Result<int> res1 = std::as_const(f1).Get();
  assert(f1.Valid());  // f1 valid here
}

if (f2.Ready()) {
  Process(std::move(f2).Get());
  assert(!f2.Valid());  // f2 invalid here
}

Exception recovering

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
auto f = yaclib::Run(tp, [] {
    if (random() % 2) {
      throw std::runtime_error{"1"};
    }
    return 42;
  }).Then([](int y) {
    if (random() % 2) {
      throw std::runtime_error{"2"};
    }
    return y + 15;
  }).Then([](int z) {  // Will not run if we have any error
    return z * 2;
  }).Then([](std::exception_ptr e) {  // Recover from error codes
    try {
      std::rethrow_exception(e);
    } catch (const std::runtime_error& e) {
      std::cout << e.what() << std::endl;
    }
    return 10;  // Some default value
  });
int x = std::move(f).Get().Value();

Error recovering

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
auto f = yaclib::Run(tp, [] {
    if (random() % 2) {
      return std::make_error_code(1);
    }
    return 42;
  }).Then([](int y) {
    if (random() % 2) {
      return std::make_error_code(2);
    }
    return y + 15;
  }).Then([](int z) {  // Will not run if we have any error
    return z * 2;
  }).Then([](std::error_code ec) {  // Recover from error codes
    std::cout << ec.value() << std::endl;
    return 10;  // Some default value
  });
int x = std::move(f).Get().Value();

Use Result for smart recovering

auto tp = yaclib::MakeThreadPool(/*threads=*/4);
auto f = yaclib::Run(tp, [] {
    if (random() % 2) {
      return std::make_error_code(1);
    }
    return 42;
  }).Then([](int y) {
    if (random() % 2) {
      throw std::runtime_error{"2"};
    }
    return y + 15;
  }).Then([](yaclib::util::Result<int> z) {
    if (!z) {
      return 10; // Some default value
    }
    return z.Value(); 
  });
int x = std::move(f).Get().Value();

Requirements

Operating systems

  • Linux
  • macOS
  • Windows
  • Android
  • iOS (theoretical)

Compilers

C++ compiler that supports standard 17 or higher.

If the library doesn't compile on some compiler satisfying this condition, please create an issue.

These compilers are tested in CI:

  • Clang-7, Clang-8, Clang-9, Clang-10, Clang-11, Clang-12
  • GCC-8, GCC-9, GCC-10, GCC-11
  • Apple Clang
  • MSVC

C++ standard

We support 17 and 20 C++ standards, we also probably support newer standards (if not, please create an issue).

We can also try support older standards. If you are interested in it, see this discussion.

Build systems

  • CMake

Releases

YACLib follows the Abseil Live at Head philosophy (update to the latest commit from the main branch as often as possible).

So we recommend using the latest commit in the main branch in your projects.

However, we realize this philosophy doesn't work for every project, so we also provide Releases.

See our release management document for more details.

Contributing

We are always open for issues and pull requests. For more details you can check following links:

Contacts

You can contact us by our emails:

Or join our Discord Server

License

YACLib is made available under MIT License. See LICENSE file for details.

We would be glad if you let us know that you're using our library.

FOSSA Status