2 * Copyright 2013 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "folly/ThreadLocal.h"
19 #include <sys/types.h>
22 #include <unordered_map>
26 #include <condition_variable>
29 #include <boost/thread/tss.hpp>
30 #include <gtest/gtest.h>
31 #include <gflags/gflags.h>
32 #include <glog/logging.h>
33 #include "folly/Benchmark.h"
35 using namespace folly;
44 static void customDeleter(Widget* w, TLPDestructionMode mode) {
45 totalVal_ += (mode == TLPDestructionMode::ALL_THREADS) * 1000;
49 int Widget::totalVal_ = 0;
51 TEST(ThreadLocalPtr, BasicDestructor) {
52 Widget::totalVal_ = 0;
53 ThreadLocalPtr<Widget> w;
55 w.reset(new Widget());
58 EXPECT_EQ(10, Widget::totalVal_);
61 TEST(ThreadLocalPtr, CustomDeleter1) {
62 Widget::totalVal_ = 0;
64 ThreadLocalPtr<Widget> w;
66 w.reset(new Widget(), Widget::customDeleter);
69 EXPECT_EQ(10, Widget::totalVal_);
71 EXPECT_EQ(10, Widget::totalVal_);
74 TEST(ThreadLocalPtr, resetNull) {
75 ThreadLocalPtr<int> tl;
78 EXPECT_TRUE(static_cast<bool>(tl));
79 EXPECT_EQ(*tl.get(), 4);
84 // Test deleting the ThreadLocalPtr object
85 TEST(ThreadLocalPtr, CustomDeleter2) {
86 Widget::totalVal_ = 0;
89 std::condition_variable cv;
95 State state = State::START;
97 ThreadLocalPtr<Widget> w;
98 t = std::thread([&]() {
99 w.reset(new Widget(), Widget::customDeleter);
102 // Notify main thread that we're done
104 std::unique_lock<std::mutex> lock(mutex);
109 // Wait for main thread to allow us to exit
111 std::unique_lock<std::mutex> lock(mutex);
112 while (state != State::EXIT) {
118 // Wait for main thread to start (and set w.get()->val_)
120 std::unique_lock<std::mutex> lock(mutex);
121 while (state != State::DONE) {
126 // Thread started but hasn't exited yet
127 EXPECT_EQ(0, Widget::totalVal_);
129 // Destroy ThreadLocalPtr<Widget> (by letting it go out of scope)
132 EXPECT_EQ(1010, Widget::totalVal_);
134 // Allow thread to exit
136 std::unique_lock<std::mutex> lock(mutex);
142 EXPECT_EQ(1010, Widget::totalVal_);
145 TEST(ThreadLocal, BasicDestructor) {
146 Widget::totalVal_ = 0;
147 ThreadLocal<Widget> w;
148 std::thread([&w]() { w->val_ += 10; }).join();
149 EXPECT_EQ(10, Widget::totalVal_);
152 TEST(ThreadLocal, SimpleRepeatDestructor) {
153 Widget::totalVal_ = 0;
155 ThreadLocal<Widget> w;
159 ThreadLocal<Widget> w;
162 EXPECT_EQ(20, Widget::totalVal_);
165 TEST(ThreadLocal, InterleavedDestructors) {
166 Widget::totalVal_ = 0;
167 ThreadLocal<Widget>* w = NULL;
169 const int wVersionMax = 2;
172 auto th = std::thread([&]() {
173 int wVersionPrev = 0;
176 std::lock_guard<std::mutex> g(lock);
177 if (wVersion > wVersionMax) {
180 if (wVersion > wVersionPrev) {
181 // We have a new version of w, so it should be initialized to zero
182 EXPECT_EQ((*w)->val_, 0);
186 std::lock_guard<std::mutex> g(lock);
187 wVersionPrev = wVersion;
192 FOR_EACH_RANGE(i, 0, wVersionMax) {
195 std::lock_guard<std::mutex> g(lock);
198 w = new ThreadLocal<Widget>();
202 std::lock_guard<std::mutex> g(lock);
203 if (thIter > thIterPrev) {
209 std::lock_guard<std::mutex> g(lock);
210 wVersion = wVersionMax + 1;
213 EXPECT_EQ(wVersionMax * 10, Widget::totalVal_);
216 class SimpleThreadCachedInt {
219 ThreadLocal<int,NewTag> val_;
228 for (const auto& i : val_.accessAllThreads()) {
235 TEST(ThreadLocalPtr, AccessAllThreadsCounter) {
236 const int kNumThreads = 10;
237 SimpleThreadCachedInt stci;
238 std::atomic<bool> run(true);
239 std::atomic<int> totalAtomic(0);
240 std::vector<std::thread> threads;
241 for (int i = 0; i < kNumThreads; ++i) {
242 threads.push_back(std::thread([&,i]() {
244 totalAtomic.fetch_add(1);
245 while (run.load()) { usleep(100); }
248 while (totalAtomic.load() != kNumThreads) { usleep(100); }
249 EXPECT_EQ(kNumThreads, stci.read());
251 for (auto& t : threads) {
256 TEST(ThreadLocal, resetNull) {
258 tl.reset(new int(4));
259 EXPECT_EQ(*tl.get(), 4);
261 EXPECT_EQ(*tl.get(), 0);
262 tl.reset(new int(5));
263 EXPECT_EQ(*tl.get(), 5);
270 folly::ThreadLocal<int, Tag> tl;
274 TEST(ThreadLocal, Movable1) {
277 EXPECT_TRUE(a.tl.get() != b.tl.get());
281 EXPECT_TRUE(a.tl.get() != b.tl.get());
284 TEST(ThreadLocal, Movable2) {
285 std::map<int, Foo> map;
293 for (auto& m : map) {
294 tls.insert(m.second.tl.get());
297 // Make sure that we have 4 different instances of *tl
298 EXPECT_EQ(4, tls.size());
301 // Yes, threads and fork don't mix
302 // (http://cppwisdom.quora.com/Why-threads-and-fork-dont-mix) but if you're
303 // stupid or desperate enough to try, we shouldn't stand in your way.
307 HoldsOne() : value_(1) { }
308 // Do an actual access to catch the buggy case where this == nullptr
309 int value() const { return value_; }
314 struct HoldsOneTag {};
316 ThreadLocal<HoldsOne, HoldsOneTag> ptr;
320 for (auto& p : ptr.accessAllThreads()) {
328 TEST(ThreadLocal, Fork) {
329 EXPECT_EQ(1, ptr->value()); // ensure created
330 EXPECT_EQ(1, totalValue());
331 // Spawn a new thread
334 bool started = false;
335 std::condition_variable startedCond;
336 bool stopped = false;
337 std::condition_variable stoppedCond;
339 std::thread t([&] () {
340 EXPECT_EQ(1, ptr->value()); // ensure created
342 std::unique_lock<std::mutex> lock(mutex);
344 startedCond.notify_all();
347 std::unique_lock<std::mutex> lock(mutex);
349 stoppedCond.wait(lock);
355 std::unique_lock<std::mutex> lock(mutex);
357 startedCond.wait(lock);
361 EXPECT_EQ(2, totalValue());
366 int v = totalValue();
368 // exit successfully if v == 1 (one thread)
369 // diagnostic error code otherwise :)
375 } else if (pid > 0) {
378 EXPECT_EQ(pid, waitpid(pid, &status, 0));
379 EXPECT_TRUE(WIFEXITED(status));
380 EXPECT_EQ(0, WEXITSTATUS(status));
382 EXPECT_TRUE(false) << "fork failed";
385 EXPECT_EQ(2, totalValue());
388 std::unique_lock<std::mutex> lock(mutex);
390 stoppedCond.notify_all();
395 EXPECT_EQ(1, totalValue());
398 // Simple reference implementation using pthread_get_specific
400 class PThreadGetSpecific {
402 PThreadGetSpecific() : key_(0) {
403 pthread_key_create(&key_, OnThreadExit);
407 return static_cast<T*>(pthread_getspecific(key_));
412 pthread_setspecific(key_, t);
414 static void OnThreadExit(void* obj) {
415 delete static_cast<T*>(obj);
421 DEFINE_int32(numThreads, 8, "Number simultaneous threads for benchmarks.");
424 BENCHMARK(FB_CONCATENATE(BM_mt_, var), iters) { \
425 const int itersPerThread = iters / FLAGS_numThreads; \
426 std::vector<std::thread> threads; \
427 for (int i = 0; i < FLAGS_numThreads; ++i) { \
428 threads.push_back(std::thread([&]() { \
429 var.reset(new int(0)); \
430 for (int i = 0; i < itersPerThread; ++i) { \
435 for (auto& t : threads) { \
440 ThreadLocalPtr<int> tlp;
442 PThreadGetSpecific<int> pthread_get_specific;
443 REG(pthread_get_specific);
444 boost::thread_specific_ptr<int> boost_tsp;
446 BENCHMARK_DRAW_LINE();
448 int main(int argc, char** argv) {
449 testing::InitGoogleTest(&argc, argv);
450 google::ParseCommandLineFlags(&argc, &argv, true);
451 google::SetCommandLineOptionWithMode(
452 "bm_max_iters", "100000000", google::SET_FLAG_IF_DEFAULT
454 if (FLAGS_benchmark) {
455 folly::runBenchmarks();
457 return RUN_ALL_TESTS();
461 Ran with 24 threads on dual 12-core Xeon(R) X5650 @ 2.67GHz with 12-MB caches
463 Benchmark Iters Total t t/iter iter/sec
464 ------------------------------------------------------------------------------
465 * BM_mt_tlp 100000000 39.88 ms 398.8 ps 2.335 G
466 +5.91% BM_mt_pthread_get_specific 100000000 42.23 ms 422.3 ps 2.205 G
467 + 295% BM_mt_boost_tsp 100000000 157.8 ms 1.578 ns 604.5 M
468 ------------------------------------------------------------------------------