Skip to content

Commit b657ecd

Browse files
committed
Add an option to use TBB as the default global thread pool
This does NOT change the default thread pool provider if you manually instantiate a thread pool, as that would change the contract of the previous definition of the thread pool and likely cause deadlocks when users expected separate threads in separate thread pools Signed-off-by: Kimball Thurston <[email protected]>
1 parent 3ebc6ed commit b657ecd

File tree

6 files changed

+144
-11
lines changed

6 files changed

+144
-11
lines changed

cmake/IlmThreadConfig.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#cmakedefine01 ILMTHREAD_THREADING_ENABLED
1919
#cmakedefine01 ILMTHREAD_HAVE_POSIX_SEMAPHORES
20+
#cmakedefine01 ILMTHREAD_USE_TBB
2021

2122
//
2223
// Current internal library namespace name

cmake/OpenEXRSetup.cmake

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ option(OPENEXR_INSTALL_PKG_CONFIG "Install OpenEXR.pc file" ON)
4242
# Whether to enable threading. This can be disabled, although thread pool and tasks
4343
# are still used, just processed immediately
4444
option(OPENEXR_ENABLE_THREADING "Enables threaded processing of requests" ON)
45+
# will use TBB for the global thread pool by default
46+
# if you create your own additional thread pools, those will NOT use
47+
# TBB by default, as it can easily cause recursive mutex deadlocks
48+
# as TBB shares a single thread pool with multiple arenas
49+
option(OPENEXR_USE_TBB "Switch internals of IlmThreadPool to use TBB by default" OFF)
4550

4651
option(OPENEXR_USE_DEFAULT_VISIBILITY "Makes the compile use default visibility (by default compiles tidy, hidden-by-default)" OFF)
4752

@@ -170,7 +175,14 @@ if(OPENEXR_ENABLE_THREADING)
170175
message(FATAL_ERROR "Unable to find a threading library, disable with OPENEXR_ENABLE_THREADING=OFF")
171176
endif()
172177
endif()
178+
if(OPENEXR_USE_TBB)
179+
find_package(TBB)
180+
if(NOT TBB_FOUND)
181+
message(FATAL_ERROR "Unable to find the OneTBB cmake library, disable with ILMTHREAD_USE_TBB=OFF or fix TBB install")
182+
endif()
183+
endif()
173184
endif()
185+
set (ILMTHREAD_USE_TBB ${OPENEXR_USE_TBB})
174186

175187
option(OPENEXR_FORCE_INTERNAL_DEFLATE "Force using an internal libdeflate" OFF)
176188
set(OPENEXR_DEFLATE_REPO "https://github.com/ebiggers/libdeflate.git" CACHE STRING "Repo path for libdeflate source")

src/lib/IlmThread/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ openexr_define_library(IlmThread
2727
)
2828

2929
if(OPENEXR_ENABLE_THREADING)
30+
if (ILMTHREAD_USE_TBB)
31+
target_link_libraries(IlmThread PUBLIC TBB::tbb)
32+
endif()
3033
target_link_libraries(IlmThread PUBLIC Threads::Threads)
3134
endif()
3235

src/lib/IlmThread/IlmThreadPool.cpp

Lines changed: 107 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@
2727
# include <unistd.h>
2828
#endif
2929

30-
ILMTHREAD_INTERNAL_NAMESPACE_SOURCE_ENTER
31-
3230
#if ILMTHREAD_THREADING_ENABLED
3331
# define ENABLE_THREADING
32+
# if ILMTHREAD_USE_TBB
33+
# include <oneapi/tbb/task_arena.h>
34+
using namespace oneapi;
35+
# endif
3436
#endif
3537

38+
ILMTHREAD_INTERNAL_NAMESPACE_SOURCE_ENTER
39+
3640
namespace
3741
{
3842

@@ -56,6 +60,7 @@ handleProcessTask (Task* task)
5660
}
5761
}
5862

63+
#ifdef ENABLE_THREADING
5964
struct DefaultThreadPoolData
6065
{
6166
Semaphore _taskSemaphore; // threads wait on this for ready tasks
@@ -81,6 +86,7 @@ struct DefaultThreadPoolData
8186
_stopping = false;
8287
}
8388
};
89+
#endif
8490

8591
} // namespace
8692

@@ -95,11 +101,11 @@ struct TaskGroup::Data
95101
Data (Data&&) = delete;
96102
Data& operator= (Data&&) = delete;
97103

104+
void waitForEmpty ();
105+
98106
void addTask ();
99107
void removeTask ();
100108

101-
void waitForEmpty ();
102-
103109
std::atomic<int> numPending;
104110
std::atomic<int> inFlight;
105111
Semaphore isEmpty; // used to signal that the taskgroup is empty
@@ -110,10 +116,11 @@ struct ThreadPool::Data
110116
using ProviderPtr = std::shared_ptr<ThreadPoolProvider>;
111117

112118
Data ();
119+
Data (ThreadPoolProvider *p);
113120
~Data ();
114121
Data (const Data&) = delete;
115122
Data& operator= (const Data&) = delete;
116-
Data (Data&&) = delete;
123+
Data (Data&&) = default;
117124
Data& operator= (Data&&) = delete;
118125

119126
ProviderPtr getProvider () const { return std::atomic_load (&_provider); }
@@ -130,6 +137,63 @@ struct ThreadPool::Data
130137
namespace
131138
{
132139

140+
#if ILMTHREAD_USE_TBB
141+
class TBBThreadPoolProvider : public ThreadPoolProvider
142+
{
143+
public:
144+
TBBThreadPoolProvider (int count) { setNumThreads (count); }
145+
TBBThreadPoolProvider (const TBBThreadPoolProvider&) = delete;
146+
TBBThreadPoolProvider&
147+
operator= (const TBBThreadPoolProvider&) = delete;
148+
TBBThreadPoolProvider (TBBThreadPoolProvider&&) = delete;
149+
TBBThreadPoolProvider& operator= (TBBThreadPoolProvider&&) = delete;
150+
~TBBThreadPoolProvider () noexcept override
151+
{
152+
finish ();
153+
}
154+
155+
int numThreads () const override
156+
{
157+
return _arena ? _arena->max_concurrency () : 1;
158+
}
159+
void setNumThreads (int count) override
160+
{
161+
if (_arena)
162+
_arena->terminate ();
163+
_arena.reset ();
164+
165+
if (count > 1)
166+
{
167+
_arena = std::make_unique<tbb::task_arena> (count);
168+
_arena->initialize ();
169+
}
170+
}
171+
172+
void addTask (Task* task) override
173+
{
174+
if (_arena)
175+
{
176+
_arena->enqueue ([=] ()
177+
{
178+
handleProcessTask (task);
179+
});
180+
}
181+
else
182+
handleProcessTask (task);
183+
}
184+
185+
void finish () override
186+
{
187+
if (_arena)
188+
_arena->terminate ();
189+
_arena.reset();
190+
}
191+
private:
192+
193+
std::unique_ptr<tbb::task_arena> _arena;
194+
};
195+
#endif
196+
133197
//
134198
// class DefaultThreadPoolProvider
135199
//
@@ -331,7 +395,8 @@ DefaultThreadPoolProvider::threadLoop (
331395
// struct TaskGroup::Data
332396
//
333397

334-
TaskGroup::Data::Data () : numPending (0), inFlight (0), isEmpty (1)
398+
TaskGroup::Data::Data ()
399+
: numPending (0), inFlight (0), isEmpty (1)
335400
{}
336401

337402
TaskGroup::Data::~Data ()
@@ -402,6 +467,12 @@ ThreadPool::Data::Data ()
402467
// empty
403468
}
404469

470+
ThreadPool::Data::Data (ThreadPoolProvider *p)
471+
: _provider (p)
472+
{
473+
// empty
474+
}
475+
405476
ThreadPool::Data::~Data ()
406477
{
407478
setProvider (nullptr);
@@ -485,6 +556,17 @@ ThreadPool::ThreadPool (unsigned nthreads)
485556
#endif
486557
}
487558

559+
// private constructor to avoid multiple calls
560+
ThreadPool::ThreadPool (Data&& d)
561+
:
562+
#ifdef ENABLE_THREADING
563+
_data (new Data (std::move (d)))
564+
#else
565+
_data (nullptr)
566+
#endif
567+
{
568+
}
569+
488570
ThreadPool::~ThreadPool ()
489571
{
490572
#ifdef ENABLE_THREADING
@@ -580,9 +662,19 @@ ThreadPool::globalThreadPool ()
580662
//
581663
// The global thread pool
582664
//
583-
665+
#ifdef ILMTHREAD_USE_TBB
666+
// Use TBB for the global thread pool by default
667+
//
668+
// We do not (currently) use this as the default thread pool
669+
// provider as it can easily cause recursive mutex deadlocks as
670+
// TBB shares a single thread pool with multiple arenas
671+
static ThreadPool gThreadPool (
672+
ThreadPool::Data (
673+
new TBBThreadPoolProvider (
674+
tbb::this_task_arena::max_concurrency ())));
675+
#else
584676
static ThreadPool gThreadPool (0);
585-
677+
#endif
586678
return gThreadPool;
587679
}
588680

@@ -596,24 +688,28 @@ unsigned
596688
ThreadPool::estimateThreadCountForFileIO ()
597689
{
598690
#ifdef ENABLE_THREADING
691+
# if ILMTHREAD_USE_TBB
692+
return tbb::this_task_arena::max_concurrency ();
693+
# else
599694
unsigned rv = std::thread::hardware_concurrency ();
600695
// hardware concurrency is not required to work
601696
if (rv == 0 ||
602697
rv > static_cast<unsigned> (std::numeric_limits<int>::max ()))
603698
{
604699
rv = 1;
605-
# if (defined(_WIN32) || defined(_WIN64))
700+
# if (defined(_WIN32) || defined(_WIN64))
606701
SYSTEM_INFO si;
607702
GetNativeSystemInfo (&si);
608703

609704
rv = si.dwNumberOfProcessors;
610-
# else
705+
# else
611706
// linux, bsd, and mac are fine with this
612707
// other *nix should be too, right?
613708
rv = sysconf (_SC_NPROCESSORS_ONLN);
614-
# endif
709+
# endif
615710
}
616711
return rv;
712+
# endif
617713
#else
618714
return 0;
619715
#endif

src/lib/IlmThread/IlmThreadPool.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ class ILMTHREAD_EXPORT_TYPE ThreadPool
150150

151151
protected:
152152
Data* _data;
153+
154+
private:
155+
ILMTHREAD_HIDDEN ThreadPool (Data&& d);
153156
};
154157

155158
class ILMTHREAD_EXPORT_TYPE Task

website/install.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Make sure these are installed on your system before building OpenEXR:
8989
* C++ compiler that supports C++11
9090
* Imath (auto fetched by CMake if not found) (https://github.com/AcademySoftwareFoundation/openexr)
9191
* libdeflate source code (auto fetched by CMake if not found) (https://github.com/ebiggers/libdeflate)
92+
* (optional) Intel's Thread Building Blocks library (TBB)
9293

9394
The instructions that follow describe building OpenEXR with CMake.
9495

@@ -388,6 +389,23 @@ local filesystem via a ``file:`` url:
388389
389390
cmake -DOPENEXR_IMAGES_REPO=file:///my/clone/of/openexr-images -DOPENEXR_IMAGES_TAG=""
390391
392+
TBB Dependency
393+
~~~~~~~~~~~~~~
394+
395+
OpenEXR can optionally use the TBB library as the default global
396+
thread pool as a thread provider. This assumes the OneAPI version of
397+
TBB which provides cmake modules. This ONLY changes the global thread
398+
pool as otherwise this can cause mutex deadlocks if you create other
399+
ThreadPools thinking that they are separate threads (i.e. the previous
400+
use case), but TBB shares actual threads and uses an arena to control
401+
thread usage.
402+
403+
To enable this, simple set the flag during config:
404+
405+
.. code-block::
406+
407+
cmake -DOPENEXR_USE_TBB=ON ...
408+
391409
Namespace Options
392410
~~~~~~~~~~~~~~~~~
393411

0 commit comments

Comments
 (0)