|
1
|
- Object-Oriented Threading
- Bartosz Milewski
|
|
2
|
- Assembly-line model
- Next operation is executed after the previous one has completed
- Outsourcing model (independent contractor)
- Specify the job, give a go-ahead
- Continue with your own work as if nothing happened
|
|
3
|
- Establish secret language
- Share data back and forth
- Be prepared for interruptions
- Poll
- Wait for completion
- Accept the result
- Kill the contractor?!
|
|
4
|
- Each thread as a separate processor
- Separate stacks of execution (local variables, function arguments)
- Shared global memory
- Reads and writes arbitrarily interleaved
- Synchronization primitives
- Runtime/Compiler supported
- Operating system
|
|
5
|
- Hire a contractor
- Create a thread of execution
- Give him a job
- Give it a function to execute
- Specify the details of the job
- Pass a void pointer (yeah, lame!)
- Give a go-ahead
- (Worry about the rest later)
|
|
6
|
- New thread executes thread procedure
- Initial data passed through void pointer
|
|
7
|
|
|
8
|
|
|
9
|
|
|
10
|
- Raw C API: generic function pointer and generic argument
- Modern C++: template parameterized by function type and argument types
- Seems like a good idea at first
- But is Thread a good abstraction?
|
|
11
|
- A thread is created
- To execute a single job
- To become a server for jobs
- A job may require arguments and return values
- A server requires a queue of jobs
- Shared data, synchronization objects
- These are all data structures!
|
|
12
|
- Create an active object with:
- Initial data (arguments)
- Shared data
- Synchronization primitives
- Private data
- Start it
- Interact with it (actively and passively)
- Delete it
|
|
13
|
- The thread is created deep inside the constructor of ActiveObject
- The this pointer is passed to the thread
- Private static method of ActiveObject serves as Thread Procedure
- All the casting is hidden inside ActiveObject which is a library class
|
|
14
|
- A framework
- The client implements pure virtual methods (in particular, Run)
- The framework calls them
- Static ThreadEntry called with the this pointer (the handler function
pattern?)
|
|
15
|
- Note: thread is born suspended
|
|
16
|
- Windows application listing a directory
- Active object Lister: lists files asynchronously, passes partial
results to Accumulator
- Active object Accumulator: accumulates listing from Lister, merges
partial results, passes them to the application
- Application: displays the listing dynamically
|
|
17
|
- Note the checking of IsDying after every wakeup
- To kill the thread, set the dying flag and release the event
- This thread may have to complete MergeChunks before it dies
- Depositing into external sink (the application) may fault—use critical
section
|
|
18
|
- How to start a thread? Who calls this?
|
|
19
|
- Naïve solution: call Kill inside the destructor and wait for it to
finish
- Kill must not be called inside the ActiveObject destructor
- Never call virtual functions from inside a destructor
|
|
20
|
- Who deletes the Active Object?
- Cannot be deleted before the thread is done running
- Waiting for the thread to terminate defeats the purpose of
multithreading
|
|
21
|
- Waiting for the tread to finish before deleting the Active Object not
good!
- Forget about the Active Object after you’re done with it
- Let the dying thread delete it
- Thread must own the object
|
|
22
|
- The thread runs
- To completion, or
- Until it’s killed
- Waits to be killed (if it has run to completion)
- Deletes the Active Object
|
|
23
|
- Note: IsDying is to be tested by the client inside Run
|
|
24
|
- Remember to start the thread
- Remember to kill the thread
- Solution: use a smart pointer object
|
|
25
|
- Example: Lister, lists files in a directory
|
|
26
|
|
|
27
|
|
|
28
|
|
|
29
|
- The client of ActiveObject wants to access data
- Add critical section to ActiveObject
- Expose locked methods to client
|
|
30
|
- ActiveObject wants to notify/return data to the client
- Expose the Sink interface to ActiveObject
- Example
- Client Lister, calls _sink->DataReady
- Object Acumulator implements Sink interface (see previous slide)
|
|
31
|
- Main application thread loops:
- Blocks in GetMessage
- Calls DispatchMessage
- Calls Windows procedure
- While WinProc executes, app is not responding
- WinProc message handlers must execute quickly
|
|
32
|
- Top-level ActiveObject passes data to its sink
- Data to be displayed by main thread
- Sink implementation calls PostMessage
- Message queue—synchronized access
|
|
33
|
- Embed auto_active in the Controller object
- Let Controller implement DataSink interface. DataReady should post a
message, the Controller should handle this message
- Initialize auto_active with an appropriate ActiveObject. Give
ActiveObject a pointer to Controller as DataSink
- When done, reset the auto_active
|