Putting threads on a solid foundation: Some Remaining Issues - - PowerPoint PPT Presentation

putting threads on a solid foundation some remaining
SMART_READER_LITE
LIVE PREVIEW

Putting threads on a solid foundation: Some Remaining Issues - - PowerPoint PPT Presentation

Putting threads on a solid foundation: Some Remaining Issues Hans-J. Boehm Multithreaded language specifications have improved, but ... Real world constraints additional language features more complications Some remaining problems 1.


slide-1
SLIDE 1

Putting threads on a solid foundation: Some Remaining Issues

Hans-J. Boehm

slide-2
SLIDE 2

Multithreaded language specifications have improved, but ...

Real world constraints ⇒ additional language features ⇒ more complications

slide-3
SLIDE 3

Some remaining problems

  • 1. Out-of-thin-air results/dependency-based
  • rdering.

○ Relaxed memory ordering is hard to specify.

  • 2. “Managed” languages: Finalization.

○ Most Java finalization uses are incorrect. ○ “Known” for 10+ years, but ...

  • 3. C++: Detached threads and object

destruction.

○ std::async() is problematic. ○ Discussed regularly in C++ standards committee.

slide-4
SLIDE 4

A word on out-of-thin-air problem for relaxed atomics

… can’t be (?) precluded from yielding x = y = 42

  • Purely a specification problem

○ Nobody has ever seen a real out-of-thin-air result. ○ Programs don’t really break. ○ We all “know what it should mean”. ○ We just can’t prove anything about ■ Java programs. ■ C++ programs using memory_order_relaxed.

  • Frustrating because the problem doesn’t seem real.
  • The other two problems result in real code breakage.

x = y; y = x;

Thread 1:: Thread 2:

slide-5
SLIDE 5

Problem 2: Java finalization

  • finalize() method runs when an object is

unreachable, as determined by garbage collector.

  • Dubious design; some problems fixed by java.lang.

ref.

  • For our purposes they’re essentially equivalent.
  • Rarely used.
  • But doing without it requires reimplementing GC.
slide-6
SLIDE 6

Common finalization idiom

Mixed language Java program:

Class T has field native_ptr, holding pointer to C++

  • bject.

T.foo(): { long x = native_ptr; native_func(x); } T.finalize(): { ... native_delete(native_ptr); native_ptr = 0; }

A thread: Finalizer thread:

slide-7
SLIDE 7

Finalization problem, continued

  • Assume this is last use
  • f this object.
  • this reference is dead.
  • x is still live.
  • Finalizer may run here.
  • native_func gets a

dangling pointer argument.

T.foo(): { long x = native_ptr; native_func(x); }

A thread:

slide-8
SLIDE 8

An official solution, since 2005

T.foo(): { long x = native_ptr; native_func(x); synchronized(this) {} } T.finalize(): { synchronized(this) {} ... native_delete(native_ptr); native_ptr = 0; }

A thread: Finalizer thread:

There are other, equally dubious, solutions. All add significant runtime cost, counterintuitive. Nobody does any of this!

slide-9
SLIDE 9

Finalization problem, continued (2)

  • Pointed out in 2003 POPL paper and 2005 JavaOne

talk.

  • Pervasive problem in every source base I’ve seen.
  • Java.lang.ref has the same problem.
  • Not limited to native code.
  • Most uses of finalization are broken.

○ The rest are mostly trivial, e.g. generate warnings.

slide-10
SLIDE 10

Possible solutions

  • Provide less expensive alternative to synchronized

(this) {} idiom. ○ Probably too little, too late.

  • Prohibit compiler dead reference elimination.

○ Viewed as too expensive for “obscure” construct.

  • Support annotation of class T that prevents compiler

eliminations of “dead” references to T. ○ Current favorite. ○ Seems to handle common cases with simple rule: ■ Annotate class if a field is invalidated by finalization or reference queue processing.

slide-11
SLIDE 11

Problem 3: Detached threads and

  • bject destruction

Detached thread: A thread that can no longer be waited for by joining it. Core problem:

  • Objects in C++ have finite lifetime.
  • There is (almost) no way to guarantee that a detached

thread completes before objects it needs are destroyed. C++11 and C++14:

  • Thread class has detach() method.
  • Library has APIs to make this sometimes somewhat

usable.

  • My recommendation: Don’t use.
slide-12
SLIDE 12

The real problem: Accidentally detached threads

What happens if an object representing an unjoined thread

  • bject goes out of scope?

Traditional answer:

  • Can no longer join, thread destructor calls detach().

Extremely dangerous!

slide-13
SLIDE 13

Accidentally detached threads (contd)

psort (begin, end) { … thread t([]{psort(mid, end);}); a: … t.join(); … } Assume: Begin, end iterators point to array allocated in caller f(). An unexpected exception is thrown at a. Now:

  • Thread t continues to run after

f() returns.

  • Thread t ends up writing to

nonexistent memory in a different thread!

  • This only took an unexpected

exception!

slide-14
SLIDE 14

Detached threads and async()

Async() is a convenient way to run a function in a thread and return a future<T>: { Vector<T> x; auto r = async ([&] { return foo(x); }) a: … r.get() …; } Exception at a must not allow late access to x ⇒ destruction of future r waits for underlying thread.

slide-15
SLIDE 15

But

Having future<T> destructor wait for thread is weird:

  • Many future<T> objects don’t have an underlying

thread.

  • Those that do behave differently from those that don’t.

○ Generic code becomes hard.

  • async(void_func); doesn’t mean what you think.

○ async(f); async(g); runs f and g sequentially!

  • Makes future<T> unusable in contexts in which blocking

is not allowed. C++11/14 provide blocking futures, but only if they are produced by async().

slide-16
SLIDE 16

Current consensus

  • std::async(), as it is, was a mistake.
  • You need separate handles on the result and underlying

“execution agent”.

  • We hope to have executors in C++17 to provide the

thread/execution agent handle.

slide-17
SLIDE 17

Questions?