Welcome, Guest. Please login or register.

Author Topic: Threads, revisited  (Read 3125 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Threads, revisited
« on: February 24, 2007, 02:53:00 PM »
Hi,

I was looking over my old system.lib code thinking about how to improve the thread classes in there.

My old (read existing) Thread class operates something like this.

When the Thread class is constructed, only the properties are set up. No exec Task is created at this point.

Thread itself is inherited from Threadable, which in turn implements a pure interface called Runnable. Runnable defines as single method, namely run().

Thread is designed to take a different instance of Runnable and execute its run method. Hence it simply implements its own inherited run() method to call this other Runnable instance's implementation of run()

So you have two options to create some threaded code.

1) Derive Threadable directly and implement the run() method to do whatever you want your created thread to execute.

2) Implement Runnable for some class and feed it to the constructor of an instance of Thread.

To kick off your thread, you call start(), which is defined in Threadable and hence naturally passed on to Thread().

This creates a new amigados Process and ultimately gets it to execute the run method.

When the run method terminates, the thread cleans up and exists.

You can request (not force) the thread to stop by calling stop(). This sets a flag in the Thread implementation that any loops you implement in your run() method (or in code they call) is required to check periodically, using Thread::stopRequested()

The stop() method will not return until the process has ended. Calling stop will also interrupt any sleep state the thread is presently in (from calling Thread::sleep()), causing it to return a value signifying that the thread has been asked to quit.

The class destructor relies on this behaviour to ensure that everything is stopped and released before the destructor returns.

So, thats basically how it works so far. However, it has the implication that every successful call to Thread::start() actually creates a new Process.

What I am actually wondering is, how to improve it. Some ideas I have are

1) Have only the first call to start() create a new Process(). Rather than the thread exiting after the return from run() and subsequent cleanup, it would simply enter a wait state, all within a "while (!destructorInvoked) { }".
Subsequent calls to start() would then terminate this sleep state and this loop would repeat, re-invoking the run method.

In theory, it ought to be faster to (re)start threads after the first call to start() since you arent creating a new Process, simply reusing an existing one.

2) Add a list of ThreadStateObserver to the implementation. This would define a simple interface that has an notify() method. Whenever the Process is about to enter run(), has just exited from run(), is about to sleep, has thrown an exception that was caught by the system around the call to run() etc... the various observers could be notified. This would be a useful improvement on the current system.

This is to decouple the monitoring of threads from the threads themselves.

Any comments or ideas welcomed :-)
int p; // A
 

Offline itix

  • Hero Member
  • *****
  • Join Date: Oct 2002
  • Posts: 2380
    • Show only replies by itix
Re: Threads, revisited
« Reply #1 on: February 24, 2007, 03:13:53 PM »
Quote

The stop() method will not return until the process has ended. Calling stop will also interrupt any sleep state the thread is presently in (from calling Thread::sleep()), causing it to return a value signifying that the thread has been asked to quit.


What if thread in question is waiting for input from a thread that called stop() ?
My Amigas: A500, Mac Mini and PowerBook
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #2 on: February 24, 2007, 04:47:47 PM »
Quote
What if thread in question is waiting for input from a thread that called stop() ?


Depending on the exact circumstances, the call to stop() throws ThreadStateViolation.

At the end of the day, there is only so much that the classes can do to prevent deadlock. If you are determined to do something stupid, you can still manage it.

When you call Thread::sleep(), it is manditory that you check on the return value. If you get a stop request, you must act accordingly.

Valid returns from sleep() include constants for timeout (sleep takes a time parameter), stop, and a number of other possible cases (eg system break which implies somebody sent your thread CTRL C)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #3 on: February 24, 2007, 05:03:34 PM »
Just to expand on that a little.

Any thread that tries to call start() or stop() on itself will throw a ThreadSecurityViolation. A Threaded instance calling a run() method other than its own will also throw this exception since Threaded::run() is the effective entry point for the a thread.

Any thread that calls setPriority() or setStackSize() on a running thread will throw a ThreadStateViolation.

The associated Lockable / ExclusiveLock / SharedLock classes (which wrap SignalSemaphore) have a mechanism to automatically throw ObjectDestroyed from anybody waiting for ownership of an object if the current owner decides to invoke its destructor. The destructor waits until each requestor has had its call "unwound" before finishing up. If your call to tryLock() or waitLock() on a Lockable object threw this exception, you are obliged never to reference it again.

Its actually not that easy to deadlock the system but as I said, if you are determined to do so, you can.

The Thread / Runnable components of the existing model can be seen here
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #4 on: February 25, 2007, 10:57:14 AM »
Do I take it that nobody agrees keeping the same Process alive  within the lifespan of the Thread object rather than spawning them on demand and letting them run their course, as it does now, is a worthwhile idea?
int p; // A
 

Offline itix

  • Hero Member
  • *****
  • Join Date: Oct 2002
  • Posts: 2380
    • Show only replies by itix
Re: Threads, revisited
« Reply #5 on: February 25, 2007, 11:05:48 AM »
It is. While spawning new processes is very cheap in AmigaDOS in Ambient, which uses threading heavily, we have always few spare processes waiting for work.
My Amigas: A500, Mac Mini and PowerBook
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #6 on: February 25, 2007, 11:26:43 AM »
I take it you implement a thread pool for that purpose, then?
int p; // A
 

Offline itix

  • Hero Member
  • *****
  • Join Date: Oct 2002
  • Posts: 2380
    • Show only replies by itix
Re: Threads, revisited
« Reply #7 on: February 25, 2007, 03:27:58 PM »
Yep, that is right.

In C++ (Ambient is purely C with MUI) you could also consider implementing thread pool where C++ thread objects allocate threads from the pool... maybe overkill though...
My Amigas: A500, Mac Mini and PowerBook
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #8 on: February 25, 2007, 03:58:31 PM »
You could, but as you suggest it might be overkill (or even pointless) considering we are now talking about a 1:1 correspondence of the Thread(able) instance and the Process it encapsulates for the entire lifespan of that Thread(able) instance.

Using a pool of already allocated Processes would make sense for the current implementation only, where the Process is spawned on the call to start() and eventually exits after it completes its call to run().
int p; // A
 

Offline itix

  • Hero Member
  • *****
  • Join Date: Oct 2002
  • Posts: 2380
    • Show only replies by itix
Re: Threads, revisited
« Reply #9 on: February 25, 2007, 04:51:27 PM »
Ah well :) Just a thought.
My Amigas: A500, Mac Mini and PowerBook
 

Offline mel_zoom

  • Full Member
  • ***
  • Join Date: Jan 2007
  • Posts: 231
    • Show only replies by mel_zoom
Re: Threads, revisited
« Reply #10 on: February 25, 2007, 10:12:29 PM »
Just one question.

What?
I love my MX5!
Please pay a visit
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Threads, revisited
« Reply #11 on: February 26, 2007, 03:00:34 PM »
Quote

mel_zoom wrote:
Just one question.

What?


Concurrency. The most fun you can have in any language that doesn't directly support it ;-)
int p; // A