Difference between revisions of "Thread.library"

From Freepascal Amiga wiki
Jump to navigation Jump to search
(Added initial paragraph unit documentation)
(→‎unit documentation: Added thread library documentation.)
 
Line 861: Line 861:
 
=== unit documentation ===
 
=== unit documentation ===
 
A bit rough around the edges. No var /var was done, and only some basic links to other units (types etc.).
 
A bit rough around the edges. No var /var was done, and only some basic links to other units (types etc.).
 +
Please note that this unit is part of contrib and the documentation is linked to package with name "ArosContrib". This needs a manual change, if you want to make it part of documentation of another package(name).
 
<source lang="xml">
 
<source lang="xml">
 +
<?xml version="1.0"?>
 +
<fpdoc-descriptions>
 +
  <package name="ArosContrib">
 +
    <!--
 +
  ====================================================================
 +
    thread
 +
  ====================================================================
 +
-->
 +
    <module name="thread">
 +
      <element name="pvoid">
 +
        <short>A pointer to nothing.</short>
 +
      </element>
 +
      <element name="ppvoid">
 +
        <short>A pointer to pvoid</short>
 +
      </element>
 +
      <element name="TThreadEntryFunction">
 +
        <short>Type definition of thread function</short>
 +
      </element>
 +
      <element name="TThreadEntryFunction.Result">
 +
        <short/>
 +
      </element>
 +
      <element name="TThreadEntryFunction.data">
 +
        <short>Optional data passed to the thread</short>
 +
      </element>
 +
      <element name="ThreadBase">
 +
        <short>A pointer to a base of a library.</short>
 +
      </element>
 +
      <element name="CreateThread">
 +
        <short>Creates a new thread.</short>
 +
        <descr>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Each thread gets its own instance of arosc.library, so it can safely call functions like printf() without conflicting with other threads. Similarly, each thread gets its own standard I/O streams, though they start attached to the same place as the task that created the thread.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CurrentThread"/>
 +
          <link id="DetachThread"/>
 +
          <link id="WaitThread"/>
 +
          <link id="WaitAllThreads"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="CreateThread.Result">
 +
        <short>Numeric thread ID, or 0 if the thread could not be started.</short>
 +
      </element>
 +
      <element name="CreateThread.entry">
 +
        <short>pointer to a function to run in the new thread.</short>
 +
      </element>
 +
      <element name="CreateThread.data">
 +
        <short>pointer to pass in the first in the first argument to function pointed to by entry.</short>
 +
      </element>
 +
      <element name="WaitThread">
 +
        <short>Blocks the current task until the requested thread exits.</short>
 +
        <descr>
 +
          <p>A thread cannot wait on itself. A thread cannot be waited on if it is detached.</p>
 +
          <p>If the thread has already completed, this call returns immediately with the thread result. Further calls to this function for that thread will fail.</p>
 +
          <p>Multiple threads can wait for a thread to complete. They will all be notified when the thread completes, and will all receive the result.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateThread"/>
 +
          <link id="CurrentThread"/>
 +
          <link id="DetachThread"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="WaitThread.Result">
 +
        <short>LTrue when the thread completed successfully. LFalse if thread could not be waited on.</short>
 +
      </element>
 +
      <element name="WaitThread.thread_id">
 +
        <short>ID of thread to detach.</short>
 +
      </element>
 +
      <element name="WaitThread.res">
 +
        <short>pointer to storage for the thread's return value. You can pass NULL here if you don't care about the return value.</short>
 +
      </element>
 +
      <element name="WaitAllThreads">
 +
        <short>Blocks the current task until all threads exit.</short>
 +
        <descr>
 +
          <p>This function will ignore detached threads.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateThread"/>
 +
          <link id="CurrentThread"/>
 +
          <link id="DetachThread"/>
 +
          <link id="WaitAllThreads"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="DetachThread">
 +
        <short>Detaches a thread from the parent process.</short>
 +
        <descr>
 +
          <p>You cannot detach a thread that is already detached.</p>
 +
          <p>Once detached, the thread is no longer accessible from any other thread.</p>
 +
        </descr>
 +
        <errors>
 +
          <p>Currently this doesn't really do anything other than make it so you can't call WaitThread() on the thread. Threads can't truly be detached from the parent process since they run in the same address space, and so when the process exits the program code and all its other resources are freed.</p>
 +
          <p>thread.library protects against this by waiting for all threads to complete (detached or not) before allowing the main process to exit.</p>
 +
          <p>Detached threads can't be truly implemented until a thread task and its allocated resources can exist independently of the process that created it.</p>
 +
        </errors>
 +
        <seealso>
 +
          <link id="CreateThread"/>
 +
          <link id="CurrentThread"/>
 +
          <link id="WaitThread"/>
 +
          <link id="WaitAllThreads"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="DetachThread.Result">
 +
        <short>LTrue if the thread was detached, LFalse if the thread was already detached or another error occured.</short>
 +
      </element>
 +
      <element name="DetachThread.thread_id">
 +
        <short>ID of thread to detach.</short>
 +
      </element>
 +
      <element name="CurrentThread">
 +
        <short>Get the ID of the running thread.</short>
 +
        <seealso>
 +
          <link id="CreateThread"/>
 +
          <link id="DetachThread"/>
 +
          <link id="WaitThread"/>
 +
          <link id="WaitAllThreads"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="CurrentThread.Result">
 +
        <short>Numeric thread ID, or 0 if this is not a thread.</short>
 +
      </element>
 +
      <element name="CreateMutex">
 +
        <short>Creates a mutual exclusion device (aka lock).</short>
 +
        <descr>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="DestroyMutex"/>
 +
          <link id="LockMutex"/>
 +
          <link id="TryLockMutex"/>
 +
          <link id="UnlockMutex"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="CreateMutex.Result">
 +
        <short>The newly created mutex, or NULL if a mutex couldn't be created.</short>
 +
      </element>
 +
      <element name="DestroyMutex">
 +
        <short>Destroys a mutex.</short>
 +
        <descr>
 +
          <p>You cannot destroy a mutex that is currently locked or has tasks waiting to lock it.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateMutex"/>
 +
          <link id="LockMutex"/>
 +
          <link id="TryLockMutex"/>
 +
          <link id="UnlockMutex"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="DestroyMutex.Result">
 +
        <short>LTrue if the mutex was destroyed, otherwise LFalse.</short>
 +
      </element>
 +
      <element name="DestroyMutex.mutex">
 +
        <short>the mutex to destroy.</short>
 +
      </element>
 +
      <element name="LockMutex">
 +
        <short>Locks a mutex. If the lock is already held, this function blocks.</short>
 +
        <descr>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateMutex"/>
 +
          <link id="DestroyMutex"/>
 +
          <link id="TryLockMutex"/>
 +
          <link id="UnlockMutex"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="LockMutex.mutex">
 +
        <short>mutex to lock.</short>
 +
      </element>
 +
      <element name="TryLockMutex">
 +
        <short>Tries to lock a mutex. If the lock is already held, this function fails</short>
 +
        <descr>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateMutex"/>
 +
          <link id="DestroyMutex"/>
 +
          <link id="LockMutex"/>
 +
          <link id="UnlockMutex"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="TryLockMutex.Result">
 +
        <short>LTrue if the lock was acquired, LFalse if the lock is already held.</short>
 +
      </element>
 +
      <element name="TryLockMutex.mutex">
 +
        <short>mutex to lock.</short>
 +
      </element>
 +
      <element name="UnlockMutex">
 +
        <short>Unlocks a locked mutex.</short>
 +
        <descr>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateMutex"/>
 +
          <link id="DestroyMutex"/>
 +
          <link id="LockMutex"/>
 +
          <link id="TryLockMutex"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="UnlockMutex.mutex">
 +
        <short>mutex to unlock.</short>
 +
      </element>
 +
      <element name="CreateCondition">
 +
        <short>Creates a condition variable.</short>
 +
        <seealso>
 +
          <link id="DestroyCondition"/>
 +
          <link id="WaitCondition"/>
 +
          <link id="SignalCondition"/>
 +
          <link id="BroadcastCondition"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="CreateCondition.Result">
 +
        <short>The newly created condition, or NULL if one couldn't be created.</short>
 +
      </element>
 +
      <element name="DestroyCondition">
 +
        <short>Destroys a condition variable.</short>
 +
        <seealso>
 +
          <link id="CreateCondition"/>
 +
          <link id="WaitCondition"/>
 +
          <link id="SignalCondition"/>
 +
          <link id="BroadcastCondition"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="DestroyCondition.Result">
 +
        <short>LTrue if the condition was destroyed, otherwise LFalse.</short>
 +
        <descr>You cannot destroy a condition variable if other threads are waiting on it.</descr>
 +
      </element>
 +
      <element name="DestroyCondition.condition">
 +
        <short>the condition variable to destroy.</short>
 +
      </element>
 +
      <element name="WaitCondition">
 +
        <short>Blocks until a condition is signaled.</short>
 +
        <descr>
 +
          <p>This function will atomically unlock the mutex and wait on the condition. The thread is suspended until the condition is signalled. After the condition is signalled, the mutex is relocked before returning to the caller.</p>
 +
          <p>The use of a mutex in conjunction with waiting on and signalling the condition ensures that no signals are missed. See <link id="SignalCondition">SignalCondition()</link> for more details.</p>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>Waiting on a condition causes the current thread to wait to receive SIGF_SINGLE from the signalling task.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateCondition"/>
 +
          <link id="DestroyCondition"/>
 +
          <link id="SignalCondition"/>
 +
          <link id="BroadcastCondition"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="WaitCondition.Result">
 +
        <short>LTrue if the condition was signaled, LFalse if an error occured.</short>
 +
      </element>
 +
      <element name="WaitCondition.condition">
 +
        <short>the condition variable to wait on.</short>
 +
      </element>
 +
      <element name="WaitCondition.mutex">
 +
        <short>a mutex that protects the condition</short>
 +
      </element>
 +
      <element name="SignalCondition">
 +
        <short>Signals a thread waiting on condition variable.</short>
 +
        <descr>
 +
          <p>Before calling this function you should lock the mutex that protects the condition. WaitCondition() automically unlocks the mutex and waits on the condition, so by locking the mutex first before sending the signal, you ensure that the signal cannot be missed. See WaitCondition() for more details.</p>
 +
          <p>If no threads are waiting on the condition, nothing happens. If more than one thread is waiting, only one will be signalled. Which one is undefined.</p>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>SIGF_SIGNAL is used to signal the selected waiting thread.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateCondition"/>
 +
          <link id="DestroyCondition"/>
 +
          <link id="WaitCondition"/>
 +
          <link id="BroadcastCondition"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="SignalCondition.condition">
 +
        <short>the condition to signal.</short>
 +
      </element>
 +
      <element name="BroadcastCondition">
 +
        <short>Signals all threads waiting on a condition variable</short>
 +
        <descr>
 +
          <p>Before calling this function you should lock the mutex that protects the condition. <link id="WaitCondition">WaitCondition()</link> atomically unlocks the mutex and waits on the condition, so by locking the mutex first before sending the signal, you ensure that the signal cannot be missed. See <link id="WaitCondition">WaitCondition()</link> for more details.</p>
 +
          <p>If no threads are waiting on the condition, nothing happens.</p>
 +
          <p/>
 +
          <p>
 +
            <b>INTERNALS:</b>
 +
          </p>
 +
          <p>SIGF_SIGNAL is used to signal the selected waiting thread.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="CreateCondition"/>
 +
          <link id="DestroyCondition"/>
 +
          <link id="WaitCondition"/>
 +
          <link id="SignalCondition"/>
 +
        </seealso>
 +
      </element>
 +
      <element name="BroadcastCondition.condition">
 +
        <short>the condition to signal</short>
 +
      </element>
 +
      <element name="ExitThread">
 +
        <short>Exits the calling thread.</short>
 +
        <descr>
 +
          <p>This function is similar to the exit() function of arosc library.</p>
 +
        </descr>
 +
        <seealso>
 +
          <link id="WaitThread"/>
 +
          <link id="WaitAllThreads"/>
 +
          <link id="clib.library/Exit">clib.library/Exit</link>
 +
        </seealso>
 +
      </element>
 +
      <element name="ExitThread.res">
 +
        <short>pointer to storage for the thread's return value. You can pass NULL here if you don't care about the return value.</short>
 +
      </element>
 +
      <short>A library to work with threads.</short>
 +
      <descr>
 +
        <p>README for thread.library</p>
 +
        <p>Author: Robert Norris (rob@cataclysm.cx)<br/>Last update: 2007-11-30</p>
 +
        <p>This is trivial library to provide basic threads and synchronisation primitives to higher-level libraries and applications. It currently provides the following:</p>
 +
        <ul>
 +
          <li>Threads</li>
 +
          <li>Mutexes</li>
 +
          <li>Condition variables</li>
 +
        </ul>
 +
        <p>This is a work-in-progress. Expect freakish things to happen if you use it. Please let me know if something doesn't work the way you want or expect it to. Both the interface and semantics are fluid, so you can get things changed if you want, but on the other hand you'll need to track changes to the library in your app.</p>
 +
        <p>This library is deliberately designed not to be POSIX threads. My hope is that it could be used to implement them, but it won't ever be that all by itself. POSIX thread semantics are more complicated than I want to deal with at this level.</p>
 +
        <p>The biggest omission at this stage is a way to force a thread to exit. Its hard to do because there's no way to know what resources the thread currently has open in order to close them. I have some ideas, but I don't know that it can be done well without proper task resource tracking.</p>
 +
        <p>In a similar vein, when your application or library closes thread.library (in an app, this will typically happen when main() exits), it will wait for any threads that are still running to finish. You'll get some warnings in the kernel log when this happens. The main process can't be allowed to exit while threads are still running as the main thread is usually holding resources that the threads need that will be deallocated when the thread exits, eg the program code itself. As long as the threads run in the same address space as the main process there's very little that can be done about this.</p>
 +
        <p>This also makes detached threads rather meaningless. The detached thread semantics are provided in the hope they can be fully implemented in the future.</p>
 +
        <p>
 +
          <b>TODO</b>
 +
        </p>
 +
        <ul>
 +
          <li>A way to gracefully ask a thread to exit (giving the thread opportunity to clean up)</li>
 +
          <li>A way to wait for all threads to finish (including detached threads, so this can be used usefully to wait for threads before exiting in main()).</li>
 +
          <li>A true way to detach running threads such that they can exist after the main process exits.</li>
 +
          <li>Can exiting the main process while threads are still running be handled better?</li>
 +
          <li>Read/write ("promotable") mutexes, where you can request to obtain a lock exclusively while already holding it as a shared lock. This can avoid races in some situations.</li>
 +
        </ul>
 +
      </descr>
 +
    </module>
 +
    <!-- thread -->
 +
  </package>
 +
</fpdoc-descriptions>
 
</source>
 
</source>

Latest revision as of 17:58, 30 November 2013

thread.library

[insert background information here]

examples

Example: Single thread

Original c-source [1]

Program CleanupAtExit;


{$MODE OBJFPC} {$H+}


uses
  amigados, contrib_thread;


Function thread_main(data: pvoid): pvoid; cdecl;
var
  i: integer;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  Writeln(ThisOutput, 'thread starting');

  for i := 0 to 10-1 do
  begin
    writeln(ThisOutPut,'count: ', i);
    DOSDelay(25);
  end;

  Writeln(ThisOutput, 'thread exiting');

  result := nil;
end;



Begin
  writeln('enter');

  CreateThread(@thread_main, nil);
  DosDelay(100);
  ExitCode := 0;

  writeln('leave');
End.

Example: Two threads

Original c-source [2]

Program twothreads;

{$MODE OBJFPC} {$H+}

uses
  amigados, contrib_thread;

Var
  t1, t2: uint32_t;

{
   Remark: in freepascal some funtionality cannot be used within the
   actual thread (read outside the main thread).
   One example being write and/or writeln routines. Those routines
   seems to miss the context of the thread that the threadlibrary
   created. Therefor the write(ln) will fail.

   Also usage of some AROS libraries will lead to failure (without
   taking proper measures) as there are different type of libraries:
   - shared (which can be used),
   - per-task (only connect to _one_ task, and another thread is
     not the same task)
   - per-opener (which should be opened on a per use base).

   Therefor failure/succes of using certain commands and/or library
   functions in the thread will depend on which libraries you open
   or don't open in a thread (= which libbase you will use).
   The same goes for the Freepascal functions (that eventually call
   AROS system libraries as well).
}
function thread_main(data: pvoid): pvoid; cdecl;
var
  id         : uint32_t;
  i          : integer;
  ThisOutput : Text;
Begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  id := CurrentThread();

  writeln(ThisOutput, '[', id, '] starting');

  for i := 0 to 10-1 do
  Begin
    WriteLn(ThisOutput, '[', id, '] count: ', i);
    DosDelay(25);
  End;

  writeln(ThisOutput,'[', id, '] exiting');

  result := nil;
End;


Begin
  writeln('enter');

  t1 := contrib_thread.CreateThread(@thread_main, nil);
  WriteLn('Created thread ', t1);

  DosDelay(100);

  t2 := CreateThread(@thread_main, nil);
  WriteLn('Created thread ', t2);

  WriteLn('waiting for thread ', t2);
  WaitThread(t2, nil);
  WriteLn('thread ', t2, ' completed');

  WriteLn('waiting for thread ', t1);
  WaitThread(t1, nil);
  WriteLn('thread ', t1, ' completed');

  ExitCode := 0;
  writeln('leave');
End.

Example: Multiple threads

Original c-source [3]

Program ThreadExit;

{$MODE OBJFPC} {$H+}

uses
  amigados, contrib_thread;


Function thread_main(data: pvoid): pvoid; cdecl;
var
  id: uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  id := CurrentThread();

  writeln(ThisOutput, '[', id, '] starting');

  DosDelay(50);

  writeln(ThisOutput, '[', id, '] exiting');

  result := pvoid(id);
end;



Var
  i   : Integer;
  id  : Array[0..10-1] of uint32_t;
  ret : uint32_t;
Begin
  writeln('enter');

  for i := 0 to 10-1 do
  begin
    id[i] := CreateThread(@thread_main, nil);
    writeln('created thread ', id[i]);
    DosDelay(25);
  end;

  for i := 0 to 10-1 do
  begin
    writeln('waiting for thread ', id[i]);
    WaitThread(id[i], @ret);
    writeln('thread ', id[i], 'return ', ret);
  end;

  ExitCode := 0;
  writeln('leave');
End.

Example: Subthreads (and lot's of them)

Original c-source [4]

Program ExitThread;


{$MODE OBJFPC} {$H+}

{
   Because this example creates _a lot_ of subthreads
   we run out of stack. So we compensate otherwise we 
   would recieve a RUNTIME ERROR 202 (Stack overflow 
   error).
}

{$M 2000000, 2000000}

uses
  amigados, contrib_thread;



Function thread_sub(data: pvoid): pvoid; cdecl;
var
  id: uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  id := CurrentThread();

  writeln(ThisOutput, '[', id, '] starting sub');

  DosDelay(50);

  writeln(ThisOutput, '[', id, '] exiting sub');

  contrib_thread.ExitThread(pvoid(id));

  result := nil;
end;



Function thread_main(data: pvoid): pvoid; cdecl;
var
  i          : integer;
  id_sub     : Array[0..10-1] of uint32_t;
  ret        : uint32_t;
  id         : uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  id := CurrentThread();

  writeln(ThisOutput, '[', id, '] starting');

  DosDelay(50);

  for i := 0 to 10-1 do
  begin
    id_sub[i] := CreateThread(@thread_sub, nil);
    writeln(ThisOutput, 'created sub thread ', id_sub[i]);
  end;

  writeln(ThisOutput, '[', id, '] exiting');

  for i := 0 to 10-1 do
  begin
    writeln(ThisOutput, 'waiting for sub thread ', id_sub[i]);
    WaitThread(id_sub[i], @ret);
    writeln(ThisOutput, 'sub thread ', id_sub[i], ' return ', ret);
  end;

  contrib_Thread.ExitThread(pvoid(id));

  result := nil;
end;



Var
  i    : Integer;
  id   : Array[0..10-1] of uint32_t;
  ret  : uint32_t;

Begin
  writeln('enter');

  for i := 0 to 10-1 do
  begin
    id[i] := CreateThread(@thread_main, nil);
    writeln('created thread ', id[i]);
  end;

  for i := 0 to 10-1 do
  begin
    writeln('waiting for thread ', id[i]);
    WaitThread(id[i], @ret);
    writeln('thread ', id[i], ' return ', ret);
  end;

  ExitCode := 0;

  writeln('leave');
End.

Example: Using a mutex amongst threads

Original c-source [5]

program mutex;


{$MODE OBJFPC} {$H+}


uses
  exec, amigados, contrib_thread;


function locker_thread(data: pvoid): pvoid; cdecl;
var
  mutex      : pvoid;
  id         : uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  mutex := data;
  id := CurrentThread();

  writeln(ThisOutput, '[',id,'] starting, locking the mutex');
  LockMutex(mutex);

  writeln(ThisOutput, '[',id,'] got it, pausing for 5s');
  DosDelay(250);

  writeln(ThisOutput, '[',id,'] unlocking the mutex');
  UnlockMutex(mutex);

  writeln(ThisOutput, '[',id,'] all done, exiting');

  result := nil;
end;


function waiter_thread(data: pvoid): pvoid; cdecl;
var
  mutex      : pvoid;
  id         : uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  mutex := data;
  id := CurrentThread();

  writeln(ThisOutput, '[',id,'] starting, locking the mutex');
  LockMutex(mutex);

  writeln(ThisOutput, '[',id,'] got it, unlocking');
  UnlockMutex(mutex);

  writeln(ThisOutput, '[',id,'] all done, exiting');

  result := nil;
end;



var
  thismutex  : pvoid;
  tw, tl : uint32_t;

begin
  writeln('enter');

  writeln('creating mutex');
  thismutex := CreateMutex();

  writeln('starting locker thread');
  tl := CreateThread(@locker_thread, thismutex);

  writeln('sleeping for 2s');
  DosDelay(100);

  writeln('starting waiter thread');
  tw := CreateThread(@waiter_thread, thismutex);

  writeln('waiting for locker thread to exit');
  WaitThread(tl, nil);

  writeln('waiting for waiter thread to exit');
  WaitThread(tw, nil);

  writeln('destroying the mutex');
  DestroyMutex(thismutex);

  writeln('all done');

  writeln('leave');
end.

Example: Signalling a condition

Original c-source [6]

program signalcond;


{$MODE OBJFPC} {$H+}


uses
  exec, amigados, contrib_thread;


Type
  PThread_Data = ^TThread_Data;
  TThread_Data = record
    mutex: pvoid;
    cond : pvoid;
  end;



function waiter_thread(data: pvoid): pvoid; cdecl;
var
  td         : pThread_Data;
  id         : uint32_t;
  ThisOutput : Text;
Begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  td := data;
  id := CurrentThread();

  writeln(ThisOutput, '[',id,'] starting, locking the mutex');
  LockMutex(td^.mutex);

  writeln(ThisOutput,'[',id,'] waiting on the condition');
  WaitCondition(td^.cond, td^.mutex);

  writeln(ThisOutput,'[',id,'] condition signalled, unlocking the mutex');
  UnlockMutex(td^.mutex);

  writeln(ThisOutput,'[',id,'] all done, exiting');

  result := nil;
end;



var
  td : pthread_data;
  tw : uint32_t;
begin
  writeln('enter');

  td := AllocMem(sizeOf(TThread_data), MEMF_PUBLIC or MEMF_CLEAR);

  writeln('creating mutex');
  td^.mutex := CreateMutex();

  writeln('creating condition');
  td^.cond := CreateCondition();

  writeln('starting waiter thread');
  tw := CreateThread(@waiter_thread, td);

  writeln('sleeping for 2s');
  DosDelay(100);

  writeln('signalling condition');
  SignalCondition(td^.cond);

  writeln('waiting for waiter thread');
  WaitThread(tw, nil);

  writeln('destroying the condition');
  DestroyCondition(td^.cond);

  writeln('destroying the mutex');
  DestroyMutex(td^.mutex);

  FreeMem(td, sizeof(TThread_Data));

  writeln('all done');

  ExitCode := 0;

  writeln('leave');
end.

Example: Broadcasting a condition

Original c-source [7]

Program BroadcastCond;

{$MODE OBJFPC} {$H+}

uses
  exec, amigados, contrib_thread;


Type
  pthread_data = ^tthread_data;
  TThread_Data = record
    mutex : pvoid;
    cond  : pvoid;
  end;



Function waiter_thread(data: pvoid): pvoid; cdecl;
var
  td         : pthread_data;
  id         : uint32_t;
  ThisOutput : Text;
begin
  {$WARNING "Grabbing Output from main thread in this manner is not thread-safe"}
  ThisOutput := System.Output;

  td := data;
  id := CurrentThread();

  writeln(ThisOutput, '[',id,'] starting, locking the mutex');
  LockMutex(td^.mutex);

  writeln(ThisOutput, '[',id,'] waiting on the condition');
  WaitCondition(td^.cond, td^.mutex);

  writeln(ThisOutput, '[',id,'] condition signalled, unlocking the mutex');
  UnlockMutex(td^.mutex);

  writeln(ThisOutput, '[',id,'] all done, exiting');

  result := nil;
end;



var
  td : pthread_data;
  i  : integer;


Begin
  writeln('enter');

  td := Exec.AllocMem(sizeof(tthread_data), MEMF_PUBLIC or MEMF_CLEAR);

  writeln('creating mutex');
  td^.mutex := CreateMutex();

  writeln('creating condition');
  td^.cond := CreateCondition();

  writeln('starting waiter threads');
  for i := 0 to 5-1
    do CreateThread(@waiter_thread, td);

  writeln('sleeping for 2s');
  DOSDelay(100);

  writeln('signalling condition');
  SignalCondition(td^.cond);

  writeln('sleeping for 2s');
  DOSDelay(100);


  writeln('broadcasting condition');
  BroadcastCondition(td^.cond);

  writeln('waiting for threads to exit');
  WaitAllThreads();

  writeln('destroying the condition');
  DestroyCondition(td^.cond);

  writeln('destroying the mutex');
  DestroyMutex(td^.mutex);


  ExecFreeMem(td, sizeof(tthread_data));
  writeln('all done');

  ExitCode := 0;

  writeln('leave');
End.

the unit

unit contrib_thread;


{$MODE OBJFPC} {$H+} {$PACKRECORDS C}

Interface

uses
  Exec;



Type
  uint32_t = LongWord;
//  pvoid    = TProcedure;
//  pvoid    = pointer;

//  ppvoid   = ^pvoid;
//  BOOL     = LongBool;


Type
  TThreadEntryFunction = Function(data: pvoid): pvoid; cdecl;



Var
  ThreadBase: pLibrary;


  Function  CreateThread(entry: TThreadEntryFunction; data: pvoid): uint32_t;
  Function  WaitThread(thread_id: uint32_t; res: ppvoid): BOOL;
  Procedure WaitAllThreads;
  Function  DetachThread(thread_id: uint32_t): BOOL;
  Function  CurrentThread: uint32_t;
  Function  CreateMutex: pvoid;
  Function  DestroyMutex(mutex: pvoid): BOOL;
  Procedure LockMutex(mutex: pvoid);
  Function  TryLockMutex(mutex: pvoid): BOOL;
  Procedure UnlockMutex(mutex: pvoid);
  Function  CreateCondition: pvoid;
  Function  DestroyCondition(condition: pvoid): BOOL;
  Function  WaitCondition(condition: pvoid; mutex: pvoid): BOOL;
  Procedure SignalCondition(condition: pvoid);
  Procedure BroadcastCondition(condition: pvoid);
  Procedure ExitThread(res: pvoid);


Implementation



Function  CreateThread(entry: TThreadEntryFunction; data: pvoid): uint32_t;
Type
  TLocalCall = Function(entry: TThreadEntryFunction; data: pvoid; LibBase: Pointer): uint32_t; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 5));
  CreateThread := Call(entry, data, ThreadBase);
End;



Function  WaitThread(thread_id: uint32_t; res: ppvoid): BOOL;
Type
  TLocalCall = Function(thread_id: uint32_t; res: ppvoid; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 6));
  WaitThread := Call(thread_id, res, ThreadBase);
End;



Procedure WaitAllThreads;
Type
  TLocalCall = Procedure(LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 7));
  Call(ThreadBase);
End;



Function  DetachThread(thread_id: uint32_t): BOOL;
Type
  TLocalCall = Function(thread_id: uint32_t; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 8));
  DetachThread := Call(thread_id, ThreadBase);
End;



Function  CurrentThread: uint32_t;
Type
  TLocalCall = Function(LibBase: Pointer): uint32_t; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 9));
  CurrentThread := Call(ThreadBase);
End;



Function  CreateMutex: pvoid;
Type
  TLocalCall = Function(LibBase: Pointer): pvoid; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 10));
  CreateMutex := Call(ThreadBase);
End;



Function  DestroyMutex(mutex: pvoid): BOOL;
Type
  TLocalCall = Function(mutex: pvoid; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 11));
  DestroyMutex := Call(mutex, ThreadBase);
End;



Procedure LockMutex(mutex: pvoid);
Type
  TLocalCall = Procedure(mutex: pvoid; LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 12));
  Call(mutex, ThreadBase);
End;



Function  TryLockMutex(mutex: pvoid): BOOL;
Type
  TLocalCall = Function(mutex: pvoid; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 13));
  TryLockMutex := Call(mutex, ThreadBase);
End;



Procedure UnlockMutex(mutex: pvoid);
Type
  TLocalCall = Procedure(mutex: pvoid; LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 14));
  Call(mutex, ThreadBase);
End;



Function  CreateCondition: pvoid;
Type
  TLocalCall = Function(LibBase: Pointer): pvoid; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 15));
  CreateCondition := Call(ThreadBase);
End;


Function  DestroyCondition(condition: pvoid): BOOL;
Type
  TLocalCall = Function(condition: pvoid; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 16));
  DestroyCondition := Call(condition, ThreadBase);
End;


Function  WaitCondition(condition: pvoid; mutex: pvoid): BOOL;
Type
  TLocalCall = Function(condition: pvoid; mutex: pvoid; LibBase: Pointer): BOOL; cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 17));
  WaitCondition := Call(condition, mutex, ThreadBase);
End;


Procedure SignalCondition(condition: pvoid);
Type
  TLocalCall = Procedure(condition: pvoid; LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 18));
  Call(condition, ThreadBase);
End;


Procedure BroadcastCondition(condition: pvoid);
Type
  TLocalCall = Procedure(condition: pvoid; LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 19));
  Call(condition, ThreadBase);
End;


Procedure ExitThread(res: pvoid);
Type
  TLocalCall = Procedure(res: pvoid; LibBase: Pointer); cdecl;
Var
  Call: TLocalCall;
Begin
  Call := TLocalCall(GetLibAdress(ThreadBase, 20));
  Call(res, ThreadBase);
End;



Initialization
  ThreadBase := OpenLibrary('thread.library',0);

Finalization
  CloseLibrary(ThreadBase);

end.

unit documentation

A bit rough around the edges. No var /var was done, and only some basic links to other units (types etc.). Please note that this unit is part of contrib and the documentation is linked to package with name "ArosContrib". This needs a manual change, if you want to make it part of documentation of another package(name).

<?xml version="1.0"?>
<fpdoc-descriptions>
  <package name="ArosContrib">
    <!--
  ====================================================================
    thread
  ====================================================================
-->
    <module name="thread">
      <element name="pvoid">
        <short>A pointer to nothing.</short>
      </element>
      <element name="ppvoid">
        <short>A pointer to pvoid</short>
      </element>
      <element name="TThreadEntryFunction">
        <short>Type definition of thread function</short>
      </element>
      <element name="TThreadEntryFunction.Result">
        <short/>
      </element>
      <element name="TThreadEntryFunction.data">
        <short>Optional data passed to the thread</short>
      </element>
      <element name="ThreadBase">
        <short>A pointer to a base of a library.</short>
      </element>
      <element name="CreateThread">
        <short>Creates a new thread.</short>
        <descr>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Each thread gets its own instance of arosc.library, so it can safely call functions like printf() without conflicting with other threads. Similarly, each thread gets its own standard I/O streams, though they start attached to the same place as the task that created the thread.</p>
        </descr>
        <seealso>
          <link id="CurrentThread"/>
          <link id="DetachThread"/>
          <link id="WaitThread"/>
          <link id="WaitAllThreads"/>
        </seealso>
      </element>
      <element name="CreateThread.Result">
        <short>Numeric thread ID, or 0 if the thread could not be started.</short>
      </element>
      <element name="CreateThread.entry">
        <short>pointer to a function to run in the new thread.</short>
      </element>
      <element name="CreateThread.data">
        <short>pointer to pass in the first in the first argument to function pointed to by entry.</short>
      </element>
      <element name="WaitThread">
        <short>Blocks the current task until the requested thread exits.</short>
        <descr>
          <p>A thread cannot wait on itself. A thread cannot be waited on if it is detached.</p>
          <p>If the thread has already completed, this call returns immediately with the thread result. Further calls to this function for that thread will fail.</p>
          <p>Multiple threads can wait for a thread to complete. They will all be notified when the thread completes, and will all receive the result.</p>
        </descr>
        <seealso>
          <link id="CreateThread"/>
          <link id="CurrentThread"/>
          <link id="DetachThread"/>
        </seealso>
      </element>
      <element name="WaitThread.Result">
        <short>LTrue when the thread completed successfully. LFalse if thread could not be waited on.</short>
      </element>
      <element name="WaitThread.thread_id">
        <short>ID of thread to detach.</short>
      </element>
      <element name="WaitThread.res">
        <short>pointer to storage for the thread's return value. You can pass NULL here if you don't care about the return value.</short>
      </element>
      <element name="WaitAllThreads">
        <short>Blocks the current task until all threads exit.</short>
        <descr>
          <p>This function will ignore detached threads.</p>
        </descr>
        <seealso>
          <link id="CreateThread"/>
          <link id="CurrentThread"/>
          <link id="DetachThread"/>
          <link id="WaitAllThreads"/>
        </seealso>
      </element>
      <element name="DetachThread">
        <short>Detaches a thread from the parent process.</short>
        <descr>
          <p>You cannot detach a thread that is already detached.</p>
          <p>Once detached, the thread is no longer accessible from any other thread.</p>
        </descr>
        <errors>
          <p>Currently this doesn't really do anything other than make it so you can't call WaitThread() on the thread. Threads can't truly be detached from the parent process since they run in the same address space, and so when the process exits the program code and all its other resources are freed.</p>
          <p>thread.library protects against this by waiting for all threads to complete (detached or not) before allowing the main process to exit.</p>
          <p>Detached threads can't be truly implemented until a thread task and its allocated resources can exist independently of the process that created it.</p>
        </errors>
        <seealso>
          <link id="CreateThread"/>
          <link id="CurrentThread"/>
          <link id="WaitThread"/>
          <link id="WaitAllThreads"/>
        </seealso>
      </element>
      <element name="DetachThread.Result">
        <short>LTrue if the thread was detached, LFalse if the thread was already detached or another error occured.</short>
      </element>
      <element name="DetachThread.thread_id">
        <short>ID of thread to detach.</short>
      </element>
      <element name="CurrentThread">
        <short>Get the ID of the running thread.</short>
        <seealso>
          <link id="CreateThread"/>
          <link id="DetachThread"/>
          <link id="WaitThread"/>
          <link id="WaitAllThreads"/>
        </seealso>
      </element>
      <element name="CurrentThread.Result">
        <short>Numeric thread ID, or 0 if this is not a thread.</short>
      </element>
      <element name="CreateMutex">
        <short>Creates a mutual exclusion device (aka lock).</short>
        <descr>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
        </descr>
        <seealso>
          <link id="DestroyMutex"/>
          <link id="LockMutex"/>
          <link id="TryLockMutex"/>
          <link id="UnlockMutex"/>
        </seealso>
      </element>
      <element name="CreateMutex.Result">
        <short>The newly created mutex, or NULL if a mutex couldn't be created.</short>
      </element>
      <element name="DestroyMutex">
        <short>Destroys a mutex.</short>
        <descr>
          <p>You cannot destroy a mutex that is currently locked or has tasks waiting to lock it.</p>
        </descr>
        <seealso>
          <link id="CreateMutex"/>
          <link id="LockMutex"/>
          <link id="TryLockMutex"/>
          <link id="UnlockMutex"/>
        </seealso>
      </element>
      <element name="DestroyMutex.Result">
        <short>LTrue if the mutex was destroyed, otherwise LFalse.</short>
      </element>
      <element name="DestroyMutex.mutex">
        <short>the mutex to destroy.</short>
      </element>
      <element name="LockMutex">
        <short>Locks a mutex. If the lock is already held, this function blocks.</short>
        <descr>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
        </descr>
        <seealso>
          <link id="CreateMutex"/>
          <link id="DestroyMutex"/>
          <link id="TryLockMutex"/>
          <link id="UnlockMutex"/>
        </seealso>
      </element>
      <element name="LockMutex.mutex">
        <short>mutex to lock.</short>
      </element>
      <element name="TryLockMutex">
        <short>Tries to lock a mutex. If the lock is already held, this function fails</short>
        <descr>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
        </descr>
        <seealso>
          <link id="CreateMutex"/>
          <link id="DestroyMutex"/>
          <link id="LockMutex"/>
          <link id="UnlockMutex"/>
        </seealso>
      </element>
      <element name="TryLockMutex.Result">
        <short>LTrue if the lock was acquired, LFalse if the lock is already held.</short>
      </element>
      <element name="TryLockMutex.mutex">
        <short>mutex to lock.</short>
      </element>
      <element name="UnlockMutex">
        <short>Unlocks a locked mutex.</short>
        <descr>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Mutexes are implemented as thin wrappers around Exec semaphores.</p>
        </descr>
        <seealso>
          <link id="CreateMutex"/>
          <link id="DestroyMutex"/>
          <link id="LockMutex"/>
          <link id="TryLockMutex"/>
        </seealso>
      </element>
      <element name="UnlockMutex.mutex">
        <short>mutex to unlock.</short>
      </element>
      <element name="CreateCondition">
        <short>Creates a condition variable.</short>
        <seealso>
          <link id="DestroyCondition"/>
          <link id="WaitCondition"/>
          <link id="SignalCondition"/>
          <link id="BroadcastCondition"/>
        </seealso>
      </element>
      <element name="CreateCondition.Result">
        <short>The newly created condition, or NULL if one couldn't be created.</short>
      </element>
      <element name="DestroyCondition">
        <short>Destroys a condition variable.</short>
        <seealso>
          <link id="CreateCondition"/>
          <link id="WaitCondition"/>
          <link id="SignalCondition"/>
          <link id="BroadcastCondition"/>
        </seealso>
      </element>
      <element name="DestroyCondition.Result">
        <short>LTrue if the condition was destroyed, otherwise LFalse.</short>
        <descr>You cannot destroy a condition variable if other threads are waiting on it.</descr>
      </element>
      <element name="DestroyCondition.condition">
        <short>the condition variable to destroy.</short>
      </element>
      <element name="WaitCondition">
        <short>Blocks until a condition is signaled.</short>
        <descr>
          <p>This function will atomically unlock the mutex and wait on the condition. The thread is suspended until the condition is signalled. After the condition is signalled, the mutex is relocked before returning to the caller.</p>
          <p>The use of a mutex in conjunction with waiting on and signalling the condition ensures that no signals are missed. See <link id="SignalCondition">SignalCondition()</link> for more details.</p>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>Waiting on a condition causes the current thread to wait to receive SIGF_SINGLE from the signalling task.</p>
        </descr>
        <seealso>
          <link id="CreateCondition"/>
          <link id="DestroyCondition"/>
          <link id="SignalCondition"/>
          <link id="BroadcastCondition"/>
        </seealso>
      </element>
      <element name="WaitCondition.Result">
        <short>LTrue if the condition was signaled, LFalse if an error occured.</short>
      </element>
      <element name="WaitCondition.condition">
        <short>the condition variable to wait on.</short>
      </element>
      <element name="WaitCondition.mutex">
        <short>a mutex that protects the condition</short>
      </element>
      <element name="SignalCondition">
        <short>Signals a thread waiting on condition variable.</short>
        <descr>
          <p>Before calling this function you should lock the mutex that protects the condition. WaitCondition() automically unlocks the mutex and waits on the condition, so by locking the mutex first before sending the signal, you ensure that the signal cannot be missed. See WaitCondition() for more details.</p>
          <p>If no threads are waiting on the condition, nothing happens. If more than one thread is waiting, only one will be signalled. Which one is undefined.</p>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>SIGF_SIGNAL is used to signal the selected waiting thread.</p>
        </descr>
        <seealso>
          <link id="CreateCondition"/>
          <link id="DestroyCondition"/>
          <link id="WaitCondition"/>
          <link id="BroadcastCondition"/>
        </seealso>
      </element>
      <element name="SignalCondition.condition">
        <short>the condition to signal.</short>
      </element>
      <element name="BroadcastCondition">
        <short>Signals all threads waiting on a condition variable</short>
        <descr>
          <p>Before calling this function you should lock the mutex that protects the condition. <link id="WaitCondition">WaitCondition()</link> atomically unlocks the mutex and waits on the condition, so by locking the mutex first before sending the signal, you ensure that the signal cannot be missed. See <link id="WaitCondition">WaitCondition()</link> for more details.</p>
          <p>If no threads are waiting on the condition, nothing happens.</p>
          <p/>
          <p>
            <b>INTERNALS:</b>
          </p>
          <p>SIGF_SIGNAL is used to signal the selected waiting thread.</p>
        </descr>
        <seealso>
          <link id="CreateCondition"/>
          <link id="DestroyCondition"/>
          <link id="WaitCondition"/>
          <link id="SignalCondition"/>
        </seealso>
      </element>
      <element name="BroadcastCondition.condition">
        <short>the condition to signal</short>
      </element>
      <element name="ExitThread">
        <short>Exits the calling thread.</short>
        <descr>
          <p>This function is similar to the exit() function of arosc library.</p>
        </descr>
        <seealso>
          <link id="WaitThread"/>
          <link id="WaitAllThreads"/>
          <link id="clib.library/Exit">clib.library/Exit</link>
        </seealso>
      </element>
      <element name="ExitThread.res">
        <short>pointer to storage for the thread's return value. You can pass NULL here if you don't care about the return value.</short>
      </element>
      <short>A library to work with threads.</short>
      <descr>
        <p>README for thread.library</p>
        <p>Author: Robert Norris (rob@cataclysm.cx)<br/>Last update: 2007-11-30</p>
        <p>This is trivial library to provide basic threads and synchronisation primitives to higher-level libraries and applications. It currently provides the following:</p>
        <ul>
          <li>Threads</li>
          <li>Mutexes</li>
          <li>Condition variables</li>
        </ul>
        <p>This is a work-in-progress. Expect freakish things to happen if you use it. Please let me know if something doesn't work the way you want or expect it to. Both the interface and semantics are fluid, so you can get things changed if you want, but on the other hand you'll need to track changes to the library in your app.</p>
        <p>This library is deliberately designed not to be POSIX threads. My hope is that it could be used to implement them, but it won't ever be that all by itself. POSIX thread semantics are more complicated than I want to deal with at this level.</p>
        <p>The biggest omission at this stage is a way to force a thread to exit. Its hard to do because there's no way to know what resources the thread currently has open in order to close them. I have some ideas, but I don't know that it can be done well without proper task resource tracking.</p>
        <p>In a similar vein, when your application or library closes thread.library (in an app, this will typically happen when main() exits), it will wait for any threads that are still running to finish. You'll get some warnings in the kernel log when this happens. The main process can't be allowed to exit while threads are still running as the main thread is usually holding resources that the threads need that will be deallocated when the thread exits, eg the program code itself. As long as the threads run in the same address space as the main process there's very little that can be done about this.</p>
        <p>This also makes detached threads rather meaningless. The detached thread semantics are provided in the hope they can be fully implemented in the future.</p>
        <p>
          <b>TODO</b>
        </p>
        <ul>
          <li>A way to gracefully ask a thread to exit (giving the thread opportunity to clean up)</li>
          <li>A way to wait for all threads to finish (including detached threads, so this can be used usefully to wait for threads before exiting in main()).</li>
          <li>A true way to detach running threads such that they can exist after the main process exits.</li>
          <li>Can exiting the main process while threads are still running be handled better?</li>
          <li>Read/write ("promotable") mutexes, where you can request to obtain a lock exclusively while already holding it as a shared lock. This can avoid races in some situations.</li>
        </ul>
      </descr>
    </module>
    <!-- thread -->
  </package>
</fpdoc-descriptions>