Making code thread-safe Kyle J. Knoepfel 25 June 2019 LArSoft - - PowerPoint PPT Presentation

making code thread safe
SMART_READER_LITE
LIVE PREVIEW

Making code thread-safe Kyle J. Knoepfel 25 June 2019 LArSoft - - PowerPoint PPT Presentation

Making code thread-safe Kyle J. Knoepfel 25 June 2019 LArSoft Workshop 2019 So youre going to make your code thread-safe 2 6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 So youre going to make your code thread-safe The


slide-1
SLIDE 1

Making code thread-safe

Kyle J. Knoepfel 25 June 2019 LArSoft Workshop 2019

slide-2
SLIDE 2

So you’re going to make your code thread-safe…

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 2

slide-3
SLIDE 3
  • The difficulty of this task depends on the context

So you’re going to make your code thread-safe…

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 3

slide-4
SLIDE 4
  • 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?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 4

So you’re going to make your code thread-safe…

slide-5
SLIDE 5
  • 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?

So you’re going to make your code thread-safe…

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 5

When writing multi-threaded code, you should always ask:

What’s the context in which this function will be called?

slide-6
SLIDE 6

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 6

slide-7
SLIDE 7

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 7

Is the object shared across threads?

Yes No

slide-8
SLIDE 8

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 8

Is the object shared across threads? Is the object mutable?

Yes No Yes No

slide-9
SLIDE 9

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 9

OK OK OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races possible

slide-10
SLIDE 10

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 10

OK OK OK OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

slide-11
SLIDE 11

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 11

OK OK OK OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

You must get out of that box!

slide-12
SLIDE 12

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 12

OK

Sometimes the easiest solution. Requires more memory.

OK OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

slide-13
SLIDE 13

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 13

OK

Sometimes the easiest solution. Requires more memory. Memory savings. Can be difficult to make things immutable.

OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

slide-14
SLIDE 14

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 14

Awkward. Will not discuss. Sometimes the easiest solution. Requires more memory. Memory savings. Can be difficult to make things immutable.

OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

slide-15
SLIDE 15

Thread-safety matrix

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 15

Awkward. Will not discuss. Sometimes the easiest solution. Requires more memory. Memory savings. Can be difficult to make things immutable.

OK

Is the object shared across threads? Is the object mutable?

Yes No Yes No

Data races guaranteed

Synchronous access Asynchronous access

Making your code thread-safe often requires a combination of methods.

slide-16
SLIDE 16
  • You must know what is shared among threads, and the contexts in which the

sharing happens.

To make your code thread-safe…

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 16

slide-17
SLIDE 17
  • 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

To make your code thread-safe…

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 17

slide-18
SLIDE 18

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 18

void test(...) { // ... } int main() { execute_with_10_threads(test, ...); }

The pattern we’ll follow

slide-19
SLIDE 19

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 19

void test() {} int main() { execute_with_10_threads(test); }

slide-20
SLIDE 20

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 20

void test() {} int main() { execute_with_10_threads(test); }

Yes

slide-21
SLIDE 21

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 21

void test() { auto i = 42; } int main() { execute_with_10_threads(test); }

slide-22
SLIDE 22

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 22

void test() { auto i = 42; } int main() { execute_with_10_threads(test); }

Yes

Each thread gets its

  • wn stack memory.
slide-23
SLIDE 23

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 23

void test(int j) { ++j; } int main() { auto i = 42; execute_with_10_threads(test, i); }

slide-24
SLIDE 24

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 24

void test(int j) { ++j; } int main() { auto i = 42; execute_with_10_threads(test, i); }

Yes

The value 42 is copied into j for each thread.

slide-25
SLIDE 25

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 25

void test() { static int j{0}; ++j; } int main() { execute_with_10_threads(test); }

slide-26
SLIDE 26

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 26

void test() { static int j{0}; ++j; } int main() { execute_with_10_threads(test); }

No

j is shared across all threads that call test

  • perator++ requires

a read and then write

slide-27
SLIDE 27

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 27

void test() { static int j{0}; int k = j; ++k; } int main() { execute_with_10_threads(test); }

slide-28
SLIDE 28

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 28

void test() { static int j{0}; int k = j; ++k; } int main() { execute_with_10_threads(test); }

Yes

Although j is shared, its value never changes k is a stack variable. This is fragile, though!

slide-29
SLIDE 29

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 29

void test() { static int j{0}; int& k = j; ++k; } int main() { execute_with_10_threads(test); }

slide-30
SLIDE 30

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 30

void test() { static int j{0}; int& k = j; ++k; } int main() { execute_with_10_threads(test); }

No

k now refers to a shared object!

  • perator++ requires

a read and then write

slide-31
SLIDE 31

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 31

void test() { static int j{0}; int& k = j; ++k; } int main() { execute_with_10_threads(test); }

No

k now refers to a shared object!

  • perator++ requires

a read and then write

One character can break a program.

slide-32
SLIDE 32

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 32

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); }

slide-33
SLIDE 33

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 33

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); }

Yes

In general, calling const-qualified C++ STL member functions is thread-safe… assuming another thread isn’t adjusting the object.

slide-34
SLIDE 34

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 34

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); }

slide-35
SLIDE 35

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 35

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); }

It depends

slide-36
SLIDE 36

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 36

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); } using MyArbitraryType = int;

slide-37
SLIDE 37

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 37

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); } using MyArbitraryType = int;

Yes

slide-38
SLIDE 38

struct MyArbitraryType { MyArbitraryType() { static int counter; ++counter; } };

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 38

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); }

slide-39
SLIDE 39

struct MyArbitraryType { MyArbitraryType() { static int counter; ++counter; } };

Part 1: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 39

void test() { MyArbitraryType t; } int main() { execute_with_10_threads(test); }

No

Although there is one ‘t’ per thread, the constructor accesses shared memory.

slide-40
SLIDE 40

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 40

class MyClass { public: void test(...) { // ... } }; int main() { MyClass obj; execute_with_10_threads(&MyClass::test, obj, ...); }

The pattern we’ll follow

slide-41
SLIDE 41

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 41

class MyClass { public: void test() {} }; int main() { MyClass obj; execute_with_10_threads(&MyClass::test, obj); }

slide-42
SLIDE 42

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 42

class MyClass { public: void test() {} }; int main() { MyClass obj; execute_with_10_threads(&MyClass::test, obj); }

Yes

slide-43
SLIDE 43

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 43

class MyClass { int count_{0}; public: void test() { ++count_; } }; int main() { MyClass obj; execute_with_10_threads(&MyClass::test, obj); }

slide-44
SLIDE 44

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 44

class MyClass { int count_{0}; public: void test() { ++count_; } }; int main() { MyClass obj; execute_with_10_threads(&MyClass::test, obj); }

No

The class data for obj (count_) is shared across threads.

  • perator++ requires

a read and then write

slide-45
SLIDE 45

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 45

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); }

slide-46
SLIDE 46

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 46

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); }

It depends

slide-47
SLIDE 47

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 47

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); } class MyOtherClass { public: int getSomething() const { return 42; } };

slide-48
SLIDE 48

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 48

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); } class MyOtherClass { public: int getSomething() const { return 42; } };

Yes

slide-49
SLIDE 49

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 49

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); } class MyOtherClass { public: int getSomething() const { static int i{42}; ++i; return i; } };

slide-50
SLIDE 50

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 50

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); } class MyOtherClass { public: int getSomething() const { static int i{42}; ++i; return i; } };

No!

slide-51
SLIDE 51

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 51

class MyClass { public: void test(MyOtherClass& oc) { auto const h = oc.getSomething(); } }; int main() { MyClass obj; MyOtherClass oc; execute_with_10_threads(&MyClass::test, obj, oc); }

slide-52
SLIDE 52

Part 2: Is it thread-safe?

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 52

class MyClass { public: void produce(art::Event& e) { auto const h = e.getValidHandle(); } }; int main() { MyClass obj; art::Event e; execute_with_10_threads(&MyClass::produce, obj, e); }

slide-53
SLIDE 53
  • Takes analysis!
  • Know what objects are shared.
  • Know when they are shared.

Determining thread-safety …

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 53

slide-54
SLIDE 54
  • Who owns your module?

Considerations for art

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 54

slide-55
SLIDE 55
  • Who owns your module?
  • art owns the module objects, which are created at run-time based on the

configuration you provide.

  • You provide the definition of the module class:

– art knows very little of your module’s definition – art calls module functions via C++ polymorphism

  • Suppose art were to call your produce function concurrently on multiple events.

Considerations for art

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 55

slide-56
SLIDE 56

Module example

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 56

  • We want to create a track from a collection of hits

void TrackMaker::produce(art::Event& e) { auto const& hits = e.getValidHandle<Hits>(tag_); unique_ptr<Track> track = trackFromHits(*hits); e.put(move(track)); }

slide-57
SLIDE 57

Module example

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 57

  • We want to create a track from a collection of hits
  • Assuming trackFromHits does not update any state, then this produce function

is thread-safe—i.e. it can be called concurrently with different art::Event

  • bjects.
  • Why?

– art guarantees that product retrieval and insertion is thread-safe – the produce function above modifies no state of the TrackMaker object

void TrackMaker::produce(art::Event& e) { auto const& hits = e.getValidHandle<Hits>(tag_); unique_ptr<Track> track = trackFromHits(*hits); e.put(move(track)); }

slide-58
SLIDE 58
  • Functions (free or member) which access a global object whose state can change,

including non-const function-scope static data.

  • Functions (free or member) which change the state of objects which were passed

as const function arguments (e.g. casting away const on an argument).

  • const non-static member functions which modify the state of the object on

which they are called (e.g. mutable members, or casting away const on this).

  • Pointer member data or data held by member data being passed as a non-const

argument to functions.

  • const member functions returning values of member variables which are pointers

to non-const items.

Indications of thread-unsafe C++ code

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 58

slide-59
SLIDE 59
  • Apply const liberally
  • Avoid using non-const static variables in functions/classes
  • To the extent possible, do not use the mutable keyword
  • Use as few global objects as possible
  • If you must use a global object, provide only const-qualified interface
  • Don’t use output arguments

General guidelines

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 59

slide-60
SLIDE 60
  • Apply const liberally
  • Avoid using non-const static variables in functions/classes
  • To the extent possible, do not use the mutable keyword
  • Use as few global objects as possible
  • If you must use a global object, provide only const-qualified interface
  • Don’t use output arguments

General guidelines

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 60

void fillInts(std::vector<int>& ints) { for (size_t i{0}; i < 42; ++i) ints.push_back(some_calculation(i)); } std::vector<int> ints; fillInts(ints); auto getInts() { std::vector<int> ints; for (size_t i{0}; i < 42; ++i) ints.push_back(some_calculation(i)); return ints; } auto const ints = getInts();

Discouraged Encouraged

slide-61
SLIDE 61
  • Apply const liberally
  • Avoid using non-const static variables in functions/classes
  • To the extent possible, do not use the mutable keyword
  • Use as few global objects as possible
  • If you must use a global object, provide only const-qualified interface
  • Don’t use output arguments

General guidelines

6/25/19 Kyle J. Knoepfel | LArSoft Workshop 2019 61

All of these are good ideas for single-threaded code. You can do this now!