Commit 0af2ca9e authored by Andrew McFague's avatar Andrew McFague Committed by Facebook GitHub Bot

folly: add folly::concurrent_lazy: a thread-safe version of folly::lazy

Summary: This introduces the concept of a *concurrent* version of `folly::lazy` that stems from our own needs for use lazily instantiated class variables in a multi-threaded environment.

Reviewed By: ot, praihan, luciang

Differential Revision: D25958581

fbshipit-source-id: 6b85402708fe9ca31b4d93607cf92f23d6a3edb5
parent c3b63c09
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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.
*/
#pragma once
#include <type_traits>
#include <utility>
#include <folly/functional/Invoke.h>
#include <folly/synchronization/DelayedInit.h>
namespace folly {
/*
* ConcurrentLazy is for thread-safe, delayed initialization of a value. This
* combines the benefits of both `folly::Lazy` and `folly::DelayedInit` to
* compute the value, once, at access time.
*
* There are a few differences between the non-concurrent Lazy, most notably:
*
* - this only safely initializes the value; thread-safety of the underlying
* value is left up to the caller.
* - the underlying types are not copyable or moveable, which means that this
* type is also not copyable or moveable.
*
* Otherwise, all design considerations from `folly::Lazy` are reflected here.
*/
template <class Func>
struct ConcurrentLazy {
using result_type = invoke_result_t<Func>;
static_assert(
!std::is_const<Func>::value,
"Func should not be a const-qualified type");
static_assert(
!std::is_reference<Func>::value,
"Func should not be a reference type");
explicit ConcurrentLazy(Func&& f) : func_(std::move(f)) {}
explicit ConcurrentLazy(const Func& f) : func_(f) {}
const result_type& operator()() const {
return value_.try_emplace_with(func_);
}
result_type& operator()() { return value_.try_emplace_with(func_); }
private:
folly::DelayedInit<result_type> value_;
mutable Func func_;
};
template <class Func>
auto concurrent_lazy(Func&& fun) {
return ConcurrentLazy<remove_cvref_t<Func>>(std::forward<Func>(fun));
}
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 <folly/ConcurrentLazy.h>
#include <functional>
#include <mutex>
#include <thread>
#include <folly/portability/GTest.h>
TEST(ConcurrentLazy, Simple) {
int computeCount = 0;
auto const val = folly::concurrent_lazy([&]() -> int {
++computeCount;
EXPECT_EQ(computeCount, 1);
return 12;
});
EXPECT_EQ(computeCount, 0);
for (int i = 0; i < 100; ++i) {
if (i > 50) {
EXPECT_EQ(val(), 12);
EXPECT_EQ(computeCount, 1);
} else {
EXPECT_EQ(computeCount, 0);
}
}
EXPECT_EQ(val(), 12);
EXPECT_EQ(computeCount, 1);
}
TEST(ConcurrentLazy, MultipleReaders) {
std::atomic_int computeCount = 0;
std::mutex m;
auto const val = folly::concurrent_lazy([&]() -> int {
++computeCount;
EXPECT_EQ(computeCount, 1);
// Block here so that we can wait for threads to pile up.
m.lock();
m.unlock();
return 12;
});
EXPECT_EQ(computeCount, 0);
// Lock the mutex while we create the readers, which will prevent the
// instantiation from completing until we've created all our readers.
m.lock();
std::vector<std::thread> readers;
for (int i = 0; i < 10; ++i) {
readers.push_back(std::thread([&] {
for (int j = 0; j < 1000; ++j) {
EXPECT_EQ(val(), 12);
}
}));
}
m.unlock();
for (auto& reader : readers) {
reader.join();
}
EXPECT_EQ(val(), 12);
EXPECT_EQ(computeCount, 1);
}
struct CopyCount {
CopyCount() = default;
CopyCount(const CopyCount&) { ++count; }
CopyCount(CopyCount&&) = default;
CopyCount& operator=(const CopyCount&) = default;
CopyCount& operator=(CopyCount&&) = default;
static int count;
bool operator()() const { return true; }
};
int CopyCount::count = 0;
TEST(ConcurrentLazy, NonLambda) {
auto const rval = folly::concurrent_lazy(CopyCount());
EXPECT_EQ(CopyCount::count, 0);
EXPECT_EQ(rval(), true);
EXPECT_EQ(CopyCount::count, 0);
CopyCount cpy;
auto const lval = folly::concurrent_lazy(cpy);
EXPECT_EQ(CopyCount::count, 1);
EXPECT_EQ(lval(), true);
EXPECT_EQ(CopyCount::count, 1);
std::function<bool()> f = [&] { return 12; };
auto const lazyF = folly::concurrent_lazy(f);
EXPECT_EQ(lazyF(), true);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment