Making code thread-safe Kyle J. Knoepfel 25 June 2019 LArSoft Workshop 2019
So you’re going to make your code thread-safe… 2 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
So you’re going to make your code thread-safe… • The difficulty of this task depends on the context 3 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
So you’re going to make your code thread-safe… • The difficulty of this task depends on the context • What language are you using? – Multi-threading in (e.g.) C++ is harder – Multi-threading in (e.g.) Go, Rust, Haskell is easier • Are you starting from scratch or retrofitting code? • Does it make sense for the code in question to be multi-threaded? 4 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
So you’re going to make your code thread-safe… • The difficulty of this task depends on the context • What language are you using? – Multi-threading in (e.g.) C++ is harder – Multi-threading in (e.g.) Go, Rust, Haskell is easier • Are you starting from scratch or retrofitting code? • Does it make sense for the code in question to be multi-threaded? When writing multi-threaded code, you should always ask: What’s the context in which this function will be called? 5 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix 6 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No 7 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Yes Is the object mutable? No 8 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Data races possible OK Yes Is the object mutable? OK OK No 9 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access OK OK Data races Yes guaranteed Is the object mutable? OK OK No 10 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access OK OK Data races Yes guaranteed Is the object mutable? OK OK No You must get out of that box! 11 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access Sometimes the easiest solution. OK Data races Yes Requires more memory. guaranteed Is the object mutable? OK OK No 12 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access Sometimes the easiest solution. OK Data races Yes Requires more memory. guaranteed Is the object mutable? OK No Memory savings. Can be difficult to make things immutable. 13 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access Sometimes the easiest solution. Data races Awkward. Yes Requires more memory. Will not discuss. guaranteed Is the object mutable? OK No Memory savings. Can be difficult to make things immutable. 14 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Thread-safety matrix Is the object shared across threads? Yes No Synchronous Asynchronous access access Sometimes the easiest solution. Data races Awkward. Yes Requires more memory. Will not discuss. guaranteed Is the object mutable? OK No Memory savings. Can be difficult to make things immutable. Making your code thread-safe often requires a combination of methods. 15 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
To make your code thread-safe… • You must know what is shared among threads, and the contexts in which the sharing happens. 16 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
To make your code thread-safe… • You must know what is shared among threads, and the contexts in which the sharing happens. • Game – Part 1 – Thread-safety and free-functions • Game – Part 2 – Thread-safety and class member functions 17 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? The pattern we’ll follow void test(...) { // ... } int main() { execute_with_10_threads(test, ...); } 18 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() {} int main() { execute_with_10_threads(test); } 19 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() {} Yes int main() { execute_with_10_threads(test); } 20 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { auto i = 42; } int main() { execute_with_10_threads(test); } 21 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { auto i = 42; Yes } Each thread gets its int main() own stack memory. { execute_with_10_threads(test); } 22 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test(int j) { ++j; } int main() { auto i = 42; execute_with_10_threads(test, i); } 23 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test(int j) { ++j; } Yes The value 42 is int main() copied into j for { each thread. auto i = 42; execute_with_10_threads(test, i); } 24 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; ++j; } int main() { execute_with_10_threads(test); } 25 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; ++j; No } j is shared across all threads that call test int main() { operator++ requires a read and then write execute_with_10_threads(test); } 26 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; int k = j; ++k; } int main() { execute_with_10_threads(test); } 27 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; int k = j; Yes ++k; Although j is shared, its } value never changes k is a stack variable. int main() { This is fragile, though! execute_with_10_threads(test); } 28 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; int& k = j; ++k; } int main() { execute_with_10_threads(test); } 29 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; int& k = j; No ++k; k now refers to } a shared object! int main() operator++ requires a read and then write { execute_with_10_threads(test); } 30 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { static int j{0}; int& k = j; No ++k; k now refers to } One character can break a program. a shared object! int main() operator++ requires a read and then write { execute_with_10_threads(test); } 31 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test(std::string const& sentence) { auto pos = sentence.find("C++17"); } int main() { std::string sentence{"I love C++17."}; execute_with_10_threads(test, sentence); } 32 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test(std::string const& sentence) Yes { auto pos = sentence.find("C++17"); In general, calling const-qualified } C++ STL member functions is int main() thread-safe… { assuming std::string sentence{"I love C++17."}; another thread execute_with_10_threads(test, sentence); isn’t adjusting } the object. 33 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); } 34 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() { MyArbitraryType t; } It depends int main() { execute_with_10_threads(test); } 35 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() using MyArbitraryType = int; { MyArbitraryType t; } int main() { execute_with_10_threads(test); } 36 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() using MyArbitraryType = int; { MyArbitraryType t; } Yes int main() { execute_with_10_threads(test); } 37 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() struct MyArbitraryType { { MyArbitraryType() MyArbitraryType t; { } static int counter; ++counter; int main() } { }; execute_with_10_threads(test); } 38 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Part 1: Is it thread-safe? void test() struct MyArbitraryType { { MyArbitraryType() MyArbitraryType t; { } static int counter; ++counter; int main() } { }; execute_with_10_threads(test); No } Although there is one ‘t’ per thread, the constructor accesses shared memory. 39 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019
Recommend
More recommend