Copyright © 2018 Peter Corke
STL provides POSIX thread primitives to MATLAB® code that has been converted to C code using the MATLAB Coder® toolchain. It allows multi-threaded operation on Linux and MacOS platforms (I don't have access to Windows to test).
STL provides threads, semaphores, mutexes, high resolution delay, timers (Linux only), logging and an embedded web server that supports templating.
To use this you must have a licence for MATLAB® and MATLAB Coder®.
More details in the project Wiki.
Also listed in MATLAB File Exchange.
If you download and test this, please send me your feedback. If you're interested in helping with development, even better, please contact me and we can make a plan. A non-exhaustive list of short- and long-term development topics is on the Wiki.
Consider this example with one main function and three additional threads. You can find this in examples/threads.
user.m
functionuser%#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\n', i, argv(i));
end% we can send timestamped messages to the log% - no need to put a new line on the end
stllog('hello world');
% note that if we send a string argument we need to convert it to a% C string
stllog('%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')
stllog('thread id %d', t1)
t2 = launch('thread2')
stllog('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');
stllog('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');
stllog('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 threadsendthread1.m
function thread1() %#codegenfor i=1:10
stllog('hello from thread1, id #%d', self());
sleep(1)
endendthread2.m
function thread2() %#codegenfor i=1:20
stllog('hello from thread2, id #%d', self());
sleep(2)
endendthread3.m
function thread3() %#codegenwhiletrue
semwait(0);
stllog('hello from thread 3');
endend>> makeThe key parts of this file is the last line
codegenuser.m thread1.m thread2.m thread3.m -config cfgThere are four files provided to codegen. The first is the user's "main" function -- the file user.m -- which is executed when the executable is run. Then we list the three additional threads.
The earlier lines in make.m simply configure the build process which is captured in the state of the coder.config object cfg which is passed as the last argument to codegen.
Ensure that the folder stl is in your MATLAB path.
The result is an executable user in the current directory which we can run
% ./user bob alice
hello world
got 3 arguments
arg 0: ./user
arg 1: bob
arg 2: alice
2018-09-16 16:52:38.281053 [user] hello world
2018-09-16 16:52:38.281793 [user] hello world
2018-09-16 16:52:38.281841 [user] thread id 1
2018-09-16 16:52:38.281850 [thread1] starting posix thread <thread1> (0xFCD5A10) 2018-09-16 16:52:38.281860 [user] thread id 2
2018-09-16 16:52:38.281869 [thread2] starting posix thread <thread2> (0xFCD5B10) 2018-09-16 16:52:38.281876 [user] waiting for thread #1 <thread1>
2018-09-16 16:52:38.281914 [thread1] hello from thread1
2018-09-16 16:52:38.281927 [thread2] hello from thread2, id #2
2018-09-16 16:52:38.281963 [thread1] hello from thread1, id #1
2018-09-16 16:52:39.286172 [thread1] hello from thread1, id #1
2018-09-16 16:52:40.285643 [thread2] hello from thread2, id #2
2018-09-16 16:52:40.286787 [thread1] hello from thread1, id #1
2018-09-16 16:52:41.286863 [thread1] hello from thread1, id #1
2018-09-16 16:52:42.286156 [thread2] hello from thread2, id #2
2018-09-16 16:52:42.287227 [thread1] hello from thread1, id #1
2018-09-16 16:52:43.287423 [thread1] hello from thread1, id #1
2018-09-16 16:52:44.289092 [thread1] hello from thread1, id #1
2018-09-16 16:52:44.289092 [thread2] hello from thread2, id #2
2018-09-16 16:52:45.289178 [thread1] hello from thread1, id #1
2018-09-16 16:52:46.292749 [thread1] hello from thread1, id #1
2018-09-16 16:52:46.292746 [thread2] hello from thread2, id #2
2018-09-16 16:52:47.297975 [thread1] hello from thread1, id #1
2018-09-16 16:52:48.297823 [thread2] hello from thread2, id #2
2018-09-16 16:52:48.299590 [thread1] MATLAB function <thread1> has returned, thread exiting
2018-09-16 16:52:48.299666 [user] thread complete #1 <thread1>
2018-09-16 16:52:50.302948 [thread2] hello from thread2, id #2
2018-09-16 16:52:52.307330 [thread2] hello from thread2, id #2
2018-09-16 16:52:53.301583 [user] cancelling thread #2 <thread2>
2018-09-16 16:52:53.301710 [user] waiting for thread #2 <thread2>
2018-09-16 16:52:53.301815 [user] thread complete #2 <thread2>
2018-09-16 16:52:55.302909 [user] creating semaphore #0 <sem1>
2018-09-16 16:52:55.302950 [user] sem id 0
2018-09-16 16:52:56.307164 [user] thread id 1
2018-09-16 16:52:56.307204 [thread3] starting posix thread <thread3> (0xFCD5BD0) 2018-09-16 16:52:56.307233 [thread3] waiting for semaphore #0 <sem1>
2018-09-16 16:52:58.311708 [user] posting semaphore #0 <sem1>
2018-09-16 16:52:58.311830 [thread3] semaphore wait complete #0
2018-09-16 16:52:58.311845 [thread3] hello from thread 3
2018-09-16 16:52:58.311855 [thread3] waiting for semaphore #0 <sem1>
2018-09-16 16:52:59.312160 [user] posting semaphore #0 <sem1>
2018-09-16 16:52:59.312197 [thread3] semaphore wait complete #0
2018-09-16 16:52:59.312204 [thread3] hello from thread 3
2018-09-16 16:52:59.312208 [thread3] waiting for semaphore #0 <sem1>
The user's main program is quite simple:
function user() %#codegenstl.log('user program starts');
webserver(8080, 'myserver');
stl.sleep(60);
endPointing a browser at port 8080 on the host running the program interacts with the MATLAB webserver code which is fairly clearly expressed.
function myserver() % called on every page requestswitch (webserver.url())
case'/'stl.log('in /')
webserver.html('home here');
case'/page1'stl.log('in /page1');
ifwebserver.isGET()
stl.log('GET request');
end
a =webserver.getarg('a');
if ~isempty(a)
stl.log('a = %s', cstring(a));
endwebserver.html('<html><body>hello <b>from</b> /page1</body></html>');
case'/page2'stl.log('in /page2')
vals.a =1;
vals.b =2;
webserver.template('templates/page2.html', vals);
case'/duck'webserver.file('duck.jpg', 'image/jpeg');
case'/input'ifwebserver.isPOST()
stl.log('POST request');
foo =webserver.postarg('Foo');
stl.log('foo = %s', cstring(foo));
elsestl.log('GET request');
endwebserver.template('templates/input.html');
endendThe switch statement is used to select the code according to the URL given, and other methods provide access to parameters of the HTTP request.
2018-09-16 15:54:28.635937 [user] user program starts
2018-09-16 15:54:28.636843 [user] web server starting on port 8080
2018-09-16 15:54:33.170370 [user] web: GET request using HTTP/1.1 for URL /page1 2018-09-16 15:54:33.170410 [WEB] in /page1
2018-09-16 15:54:33.170416 [WEB] GET request
2018-09-16 15:54:33.170421 [WEB] a = 7
2018-09-16 15:54:33.170425 [WEB] web_html: <html><body>hello <b>from</b> /page1</body></html></body></html>
Note the arguements ?a=7&b=12 on the end of the URL. These are GET arguments of the form key=value, and the method getarg provides access to them by key. In this case we get the value of the key a.
Note also, that log messages from the web server function are listed as coming from the WEB thread, which is created by webserver().
2018-09-16 15:39:12.816790 [WEB] web: GET request using HTTP/1.1 for URL /page2 2018-09-16 15:39:12.816822 [WEB] in /page2
2018-09-16 15:39:12.816827 [WEB] web_setvalue: a 1
2018-09-16 15:39:12.816849 [WEB] web_setvalue: b 2
2018-09-16 15:39:12.816854 [WEB] web_template: templates/page2.html
The template file is sent to the browser with substitutions. The page2.html looks like
<html><body><p>This is a test page</p><p>a = <TMPL_VARname="a"></p><p>b = <TMPL_VARname="b"></p></body></html>and the values of the fields of the struct vals are substituted for the corresonding named TMPL_VAR tags.
2018-09-16 15:36:08.881139 [WEB] web: GET request using HTTP/1.1 for URL /duck 2018-09-16 15:36:08.881159 [WEB] web_file: duck.jpg, type image/jpeg
2018-09-16 15:36:08.881208 [WEB] file is 83234 bytes
The local file duck.png is sent to the browser as an image/jpeg filetype.
2018-09-16 16:32:00.035029 [user] web: GET request using HTTP/1.1 for URL /input 2018-09-16 16:32:00.035101 [WEB] input called
2018-09-16 16:32:00.035109 [WEB] GET request
2018-09-16 16:32:00.035118 [WEB] web_template: templates/input.html
2018-09-16 16:32:04.385387 [WEB] web: POST request using HTTP/1.1 for URL /input 2018-09-16 16:32:04.385580 [WEB] web: POST request using HTTP/1.1 for URL /input 2018-09-16 16:32:04.385623 [WEB] POST [Foo] = 27
2018-09-16 16:32:04.385634 [WEB] POST [button] = Button2
2018-09-16 16:32:04.385655 [WEB] web: POST request using HTTP/1.1 for URL /input 2018-09-16 16:32:04.385666 [WEB] input called
2018-09-16 16:32:04.385671 [WEB] POST request
2018-09-16 16:32:04.385676 [WEB] foo = 27
2018-09-16 16:32:04.385681 [WEB] web_template: templates/input.html
This example is rather more complex. The page is requested with a standard GET request and the HTML file input.html is returned to the browser
<html><body><p>This is a page to test POST</p><formaction="" method="post"><p>Enter value of foo:
<!-- POST key = Foo, POST value is entered tect --><inputtype="text" value="0" name="Foo" /><!-- button label is given by value, POST key = button, POST value = Button1/2 --><p><inputtype="submit" value="Button1" name="button" /></p><p><inputtype="submit" value="Button2" name="button" /></p></form></body></html>The browser displays a form with a text input box and two buttons. When either button (or a newline) is entered, the browser sends the contents of the text box and the button that pushed as name=value pairs. The postarg method gets the value of the textbox which has name=Foo. The diagnostic messages show that Button2 was pressed, and this could be tested by accessing the value for the name button.
The POST request must return a value, and in this case it is template file.
Created using Sublime3 with awesome packages MarkdownEditing, MarkdownPreview and Livereload for WYSIWYG markdown editing.



