Skip to content

Commit

Permalink
Initial code push
Browse files Browse the repository at this point in the history
  • Loading branch information
petercorke committed Aug 27, 2018
1 parent 889428e commit 9aaa6da
Show file tree
Hide file tree
Showing 22 changed files with 955 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
user
codegen/
*~
212 changes: 212 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Simple thread library (STL) for MATLAB Coder

STL provides POSIX thread primitives to MATLAB code that has been converted to C code using the MATLAB Coder toolchain. This allows multithread operation on Linux, MacOS and Windows platforms.

STL provides threads, semaphores, mutexes, high resolution delay, and logging.

## Design principles

STL supports the key semantics of POSIX threads, semaphores and mutexes. Each of these objects is referenced by a small integer, starting at zero upto a defined maximum. The actual handles are kept in arrays within stl.c, the maximum number of threads, semaphores and mutexes (currently 8) can be adjusted by parameters in stl.c.

A typical STL program comprises a file `user.m` which contains the main thread of execution which is executed when the binary is launched. Each additional thread must be defined in a separate m-file. limitation of MATLAB Coder is that each thread has to be in a separate file (an "entry point function").

When a thread is launched we can pass a uint32 to the thread function. To do this we need to declare a uint32 argument to `codegen` using
```matlab
codegen ... thread.m -args uint32(0) ...
```


## Command line arguments

```shell
% user alice bob
```

Command line arguments are kept and made available by STL functions.

`argc` returns the number of arguments, always one or more. `argv(i)` returns the i'th argument as a string. i=0 gives the name of the command, and the maximum value of i is one less than the value returned by `argc`.


## Debugging

Detailed logging of all events to the log channel can be enabled.

```matlab
stldebug(true)
```

Events include object creation, thread launch and exit, semaphore post and wait, mutex lock and unlock. Example log file enabled by debugging is:

```matlab
2018-08-26 17:46:00.174853: hello from thread1, arg=24, id #0
2018-08-26 17:46:01.166834: hello from thread2, id #1
2018-08-26 17:46:01.178341: posix thread <thread1> has returned
2018-08-26 17:46:01.178493: thread complete #0 <thread1>
2018-08-26 17:46:03.167094: hello from thread2, id #1
```

## STL function summary

The following table lists all STL functions. Where relevant there is a pointer to details of the underlying POSIX function.

MATLAB function | Purpose | POSIX reference |
---:|---:|---:|
argc | Get number of command line arguments | |
argv | Get command line argument | |
rtmlog | Print to log | |
launch | Create a thread | [`pthread_create`](http://man7.org/linux/man-pages/man3/pthread_create.3.html) |
cancel | Cancel a thread | [`pthread_cancel`](http://man7.org/linux/man-pages/man3/pthread_cancel.3.html) |
join | Wait for thread to complete | [`pthread_join`](http://man7.org/linux/man-pages/man3/pthread_join.3.html) |
semnew | Create semaphore | [`sem_create`](http://man7.org/linux/man-pages/man3/pthread_create.3.html) |
sempost | Post semaphore | [`sem_post`](http://man7.org/linux/man-pages/man3/sem_create.2.html) |
semwait | Wait for semaphore | [`sem_wait`](http://man7.org/linux/man-pages/man3/sem_wait.2.html) |
mutex | Create a mutex | [`pthread_mutex_create`](http://man7.org/linux/man-pages/man3/pthread_mutex_create.3.html) |
mutlock | Lock a mutex | [`pthread_mutex_lock`](http://man7.org/linux/man-pages/man3/pthread_lock.3.html) |
mutunlock | Unlock a mutex | [`pthread_mutex_unlock`](http://man7.org/linux/man-pages/man3/pthread_unlock.3.html) |

## An example

Consider this example with one main function and three additional threads.
user.m
```matlab
function user %#codegen
% we can print to stderr
fprintf(2, 'hello world\n');
% we have access to command line arguments
fprintf(2, 'got %d arguments\n', argc() );
for i=0:argc()-1
fprintf(2, ' arg %d: %s', i, argv(i));
end
% we can send timestamped messages to the log
% - no need to put a new line on the end
rtmlog('hello world');
% note that if we send a string argument we need to convert it to a
% C string
rtmlog('%s', cstring('hello world'));
% now we can launch a couple of threads, see thread1.m and thread2.m
% launching returns a thread id, a small integer
t1 = launch('thread1', 24) % pass a value to this thread
rtmlog('thread id %d', t1)
t2 = launch('thread2')
rtmlog('thread id %d', t2)
join(t1); % wait for thread 1 to finish
sleep(5);
cancel(t2); % kill thread 2 and wait for it
join(t2)
sleep(2)
% create a semaphore
s1 = newsemaphore('sem1');
rtmlog('sem id %d', s1);
sleep(1)
% launch a new thread, see thread3.m
% it just waits for the semaphore, then prints a message
t3 = launch('thread3', 42);
rtmlog('thread id %d', t3);
sleep(2);
sempost(0); % wake up thread 3
sleep(1);
sempost(0); % wake up thread 3
sleep(2);
% done, exiting will tear down all the threads
end
```

thread1.m
```matlab
function thread1(arg) %#codegen
for i=1:10
rtmlog('hello from thread1, arg=%d, id #%d', arg, self());
sleep(1)
end
end
```

thread2.m
```matlab
function thread2() %#codegen
for i=1:20
rtmlog('hello from thread2, id #%d', self());
sleep(2)
end
end
```

thread3.m
```matlab
function thread3() %#codegen
while true
semwait(0);
rtmlog('hello from thread 3');
end
end
```

The results are
```
hello world
got 3 arguments
arg 0: ./user arg 1: alice arg 2: bob2018-08-26 17:45:51.152678: hello world
2018-08-26 17:45:51.153433: hello world
2018-08-26 17:45:51.153513: thread id 0
2018-08-26 17:45:51.153535: thread id 1
2018-08-26 17:45:51.153541: waiting for thread #0 <thread1>
2018-08-26 17:45:51.153548: starting posix thread <thread2> (0xE4CD290)
2018-08-26 17:45:51.153546: starting posix thread <thread1> (0xE4CD1D0)
2018-08-26 17:45:51.153593: hello from thread1, arg=24, id #0
2018-08-26 17:45:51.153596: hello from thread2, id #1
2018-08-26 17:45:52.154518: hello from thread1, arg=24, id #0
2018-08-26 17:45:53.156651: hello from thread2, id #1
2018-08-26 17:45:53.156660: hello from thread1, arg=24, id #0
2018-08-26 17:45:54.158095: hello from thread1, arg=24, id #0
2018-08-26 17:45:55.156826: hello from thread2, id #1
2018-08-26 17:45:55.163124: hello from thread1, arg=24, id #0
2018-08-26 17:45:56.163432: hello from thread1, arg=24, id #0
2018-08-26 17:45:57.158195: hello from thread2, id #1
2018-08-26 17:45:57.166942: hello from thread1, arg=24, id #0
2018-08-26 17:45:58.167204: hello from thread1, arg=24, id #0
2018-08-26 17:45:59.163463: hello from thread2, id #1
2018-08-26 17:45:59.172467: hello from thread1, arg=24, id #0
2018-08-26 17:46:00.174853: hello from thread1, arg=24, id #0
2018-08-26 17:46:01.166834: hello from thread2, id #1
2018-08-26 17:46:01.178341: posix thread <thread1> has returned
2018-08-26 17:46:01.178493: thread complete #0 <thread1>
2018-08-26 17:46:03.167094: hello from thread2, id #1
2018-08-26 17:46:05.170848: hello from thread2, id #1
2018-08-26 17:46:06.183087: cancelling thread #1 <thread2>
2018-08-26 17:46:06.183179: waiting for thread #1 <thread2>
2018-08-26 17:46:06.183254: thread complete #1 <thread2>
2018-08-26 17:46:08.185224: creating semaphore #0 <sem1>
2018-08-26 17:46:08.185266: sem id 0
2018-08-26 17:46:09.189632: thread id 0
2018-08-26 17:46:09.189680: starting posix thread <thread3> (0xE4CD350)
2018-08-26 17:46:09.189707: waiting for semaphore #0 <sem1>
2018-08-26 17:46:11.193260: posting semaphore #0 <sem1>
2018-08-26 17:46:11.193375: semaphore wait complete #0
2018-08-26 17:46:11.193395: hello from thread 3
2018-08-26 17:46:11.193426: waiting for semaphore #0 <sem1>
2018-08-26 17:46:12.198523: posting semaphore #0 <sem1>
2018-08-26 17:46:12.198594: semaphore wait complete #0
2018-08-26 17:46:12.198606: hello from thread 3
2018-08-26 17:46:12.198615: waiting for semaphore #0 <sem1>
```
## Future work

STL has been developed using MATLAB 2018bPRE under MacOS High Sierra.

Future development possibilities include:

1. Thread priority.
2. Handling globals.
3. Passing more complex arguments to threads, eg. structs.
4. Add condition variables as an additional synchronization mechanism.
5. Cancelation points.
33 changes: 33 additions & 0 deletions make.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
% make.m

cfg = coder.config('exe')
cfg.TargetLang = 'C';
cfg.MultiInstanceCode = true;

cfg.GenerateReport = true;
%cfg.LaunchReport = true;

% build options
cfg.GenerateExampleMain = 'DoNotGenerate';
%cfg.GenCodeOnly = true;

cfg.GenerateComments = true;
cfg.MATLABSourceComments = true; % lots of comments
cfg.PreserveVariableNames = 'UserNames';

% want to include some files from this directory, but this doesnt work??
cfg.CustomSource = 'main.c stl.c'
cfg.CustomInclude = 'stl';

cfg.BuildConfiguration = 'Faster Builds';
%cfg.BuildConfiguration = 'Debug';

% would be nice if could set make -j to get some parallel building
% happening.

%{
cfg.InlineThreshold = 0; % tone down the aggressive inlining
codegen user.m thread1.m -args int32(0) thread2.m thread3.m -O disable:inline -config cfg
%}

codegen user.m thread1.m -args int32(0) thread2.m thread3.m -config cfg
6 changes: 6 additions & 0 deletions stl/argc.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function ac = argc()
coder.cinclude('stl.h');

ac = int32(0);
ac = coder.ceval('stl_argc'); % evaluate the C function
end
20 changes: 20 additions & 0 deletions stl/argv.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
function s = argv(a)
coder.cinclude('stl.h');

s = '';
coder.varsize('s');

BUFSIZ = 256;
buf = char(zeros(1,BUFSIZ)); % create a buffer to write into, all nulls

coder.ceval('stl_argv', a, coder.wref(buf), BUFSIZ); % evaluate the C function

% find the end of the string, where the first unwritten null is
for i=1:BUFSIZ-1
if buf(i) == 0
% found a null, return variable length array up to here
s = buf(1:i-1);
return;
end
end
end
5 changes: 5 additions & 0 deletions stl/cancel.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function cancel(id)
coder.cinclude('stl.h');

coder.ceval('stl_thread_cancel', id ); % evaluate the C function
end
3 changes: 3 additions & 0 deletions stl/cstring.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function cs = cstring(s)
cs = [s char(0)];
end
5 changes: 5 additions & 0 deletions stl/join.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function join(id)
coder.cinclude('stl.h');

coder.ceval('stl_thread_join', id ); % evaluate the C function
end
9 changes: 9 additions & 0 deletions stl/launch.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function tid = launch(name, arg)
coder.cinclude('stl.h');

if nargin < 2
arg = 0;
end
tid = int32(0);
tid = coder.ceval('stl_thread_create', cstring(name), int32(arg)); % evaluate the C function
end
25 changes: 25 additions & 0 deletions stl/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
** main.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stl.h"
#include "user.h"
#include "user_initialize.h"
#include "user_terminate.h"

int
main(int argc, char **argv)
{
// initialize the thread library
stl_initialize(argc, argv);

// call the user's function
user_initialize();
user();
user_terminate();

return 0;
}
3 changes: 3 additions & 0 deletions stl/mutex.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function mutex(name)
coder.ceval('stl_launch', [name 0]); % evaluate the C function
end
6 changes: 6 additions & 0 deletions stl/newsemaphore.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function sid = semaphore(name)
coder.cinclude('stl.h');

sid = int32(0);
sid = coder.ceval('stl_sem_create', cstring(name)); % evaluate the C function
end
6 changes: 6 additions & 0 deletions stl/self.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function id = self()
coder.cinclude('stl.h');

id = int32(0);
id = coder.ceval('stl_thread_self'); % evaluate the C function
end
5 changes: 5 additions & 0 deletions stl/sempost.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function sempost(id)
coder.cinclude('stl.h');

coder.ceval('stl_sem_post', id); % evaluate the C function
end
8 changes: 8 additions & 0 deletions stl/semwait.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function semwait(id, wait)
coder.cinclude('stl.h');

if nargin < 2
wait = int32(0);
end
coder.ceval('stl_sem_wait', id, wait); % evaluate the C function
end
6 changes: 6 additions & 0 deletions stl/sleep.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

function sleep(t)
coder.cinclude('stl.h');
coder.ceval('stl_sleep', t); % evaluate the C function
end

Loading

0 comments on commit 9aaa6da

Please sign in to comment.