Forums > Dev

multi-threading in externals

May 28, 2008 | 2:43 pm

Dear List,

I developed an external which connects to a streaming TCP/IP socket. In
order to receive the (time-critical) data as fast as possible, the object
has to handle the receiving and parsing of the data in a separate thread,
otherwise the max scheduler locks up. I have instantiated a Posix thread to
receive and parse the data, which writes to a shared data buffer in my
object. At the same time a Max clock is running (in the main scheduler) for
reading the buffer and writing the data to an outlet.

I use a circular buffer to pass the data between the Posix thread and the
clock function. I first used std::vector for it, but it didn’t like that at
all (debug assertion failure related to iterators), so now I use a plain
array which seems to be ok. The object works fine for the time being, but
I’m pretty sure what I’m doing it not thread safe.

Can I make use of critical regions or mutexes to protect the data buffer? Is
there a Max native way to spawn a new thread and use critical regions with
it? I’ve read the section about critical regions, but I’m just not sure if
it applies to my situation.

Quote:
"So unless you are completely sure of what you are doing, and absolutely
need to make use of multiple critical regions to protect your code, we
suggest you use the global critical region."

So yeah, I’m clearly not completely sure:)

In earlier attempts I tried using the scheduler_set() and family to create a
new scheduler instead of using Posix, but this got me into trouble more than
anything. I find the documentation about this is subject very sparse at
best, and a search in the archives didn’t turn up anything either. The text
in the paragraph "creating schedulers" in the writingexternals.pdf seems to
be messed up.

Cheers,
Thijs


May 28, 2008 | 3:59 pm

On 2008 May 28, at 9:43 AM, Thijs Koerselman wrote:

> Dear List,
>
> I developed an external which connects to a streaming TCP/IP socket.
> In order to receive the (time-critical) data as fast as possible,
> the object has to handle the receiving and parsing of the data in a
> separate thread, otherwise the max scheduler locks up. I have
> instantiated a Posix thread to receive and parse the data, which
> writes to a shared data buffer in my object. At the same time a Max
> clock is running (in the main scheduler) for reading the buffer and
> writing the data to an outlet.

[snip]

> Can I make use of critical regions or mutexes to protect the data
> buffer? Is there a Max native way to spawn a new thread and use
> critical regions with it? I’ve read the section about critical
> regions, but I’m just not sure if it applies to my situation.

It seems like you actually have two issues here: one is working with
another thread, and the other is about thread-safety.

There is a Max native way to create and use new threads. You can find
the relevant coding support in ext_systhread.h. I don’t think it’s
really documented yet.

In Tap.Tools 3 there is a new object (tap.svn) which is a Subversion
client wrapped up as a Max external. This is still a work in
progress, and the code is a bit of a mess, but it does use the
systhread API defined in Max. I’ve attached the source in the hope it
will be useful. Here are the relevant bits:

1. In the struct:
t_systhread thread;
short thread_running;

2. In the free routine, you need to make sure that you don’t hang Max:
if(x->thread_running > 0)
systhread_join(x->thread, NULL);// avoid deadlock if thread should
somehow be dead.

3. Down in maxsvn_anything() we our flag in the struct and call a
method, which executes in a new thread (whose handle we are storing in
our instance):
x->thread_running = 1;
systhread_create((method)maxsvn_doanything, x, 0, 0, 0, &x->thread);

4. When maxsvn_doanything() is done, and thus we don’t need the thread
anymore, we do this:
x->thread_running = 0;
systhread_exit(0);

In your case, you might create the thread with method that implements
an infinite loop (I’m not entire sure what you are doing). If you do,
you may also want to make sure that your thread is throttled somehow,
such as using systhread_sleep(). Something like this:
while(1){
// look to see if there is something to do
// if so, then do it…

// relinquish the processor briefly so Max can do the other things
it needs to do
systhread_sleep(25);
}

I hope this helps.
best,
Tim


May 28, 2008 | 9:32 pm

2008/5/28 Timothy Place :

> It seems like you actually have two issues here: one is working with
> another thread, and the other is about thread-safety.
>
> There is a Max native way to create and use new threads. You can find the
> relevant coding support in ext_systhread.h. I don’t think it’s really
> documented yet.
>
> In Tap.Tools 3 there is a new object (tap.svn) which is a Subversion client
> wrapped up as a Max external. This is still a work in progress, and the
> code is a bit of a mess, but it does use the systhread API defined in Max.
> I’ve attached the source in the hope it will be useful. Here are the
> relevant bits:
>
>
Hi Tim,

Thanks a lot! That’s exactly what I need. It looks really similar to what I
do with Posix atm. I’m glad I can get rid of that, because it was adding an
extra dependency since its not natively supported on windows.

I do use an infinite loop, but I don’t need it to sleep. It’s automatically
waiting for the socket to receive data, and I need to parse as soon as I get
it. It works fine like this and hardly uses any cpu so I’m happy.

You didn’t mention critical regions, so just to be clear; I can create a
mutex by using the critical enter/exit with thread 0, right? Or is this
something I have to create my own critical region struct for?

Best,
Thijs


May 28, 2008 | 9:39 pm

On 2008 May 28, at 4:32 PM, Thijs Koerselman wrote:

>
> 2008/5/28 Timothy Place :
> It seems like you actually have two issues here: one is working with
> another thread, and the other is about thread-safety.
>
> There is a Max native way to create and use new threads. You can
> find the relevant coding support in ext_systhread.h. I don’t think
> it’s really documented yet.
>
> In Tap.Tools 3 there is a new object (tap.svn) which is a Subversion
> client wrapped up as a Max external. This is still a work in
> progress, and the code is a bit of a mess, but it does use the
> systhread API defined in Max. I’ve attached the source in the hope
> it will be useful. Here are the relevant bits:
>
>
> Hi Tim,
>
> Thanks a lot! That’s exactly what I need. It looks really similar to
> what I do with Posix atm. I’m glad I can get rid of that, because it
> was adding an extra dependency since its not natively supported on
> windows.
>
> I do use an infinite loop, but I don’t need it to sleep. It’s
> automatically waiting for the socket to receive data, and I need to
> parse as soon as I get it. It works fine like this and hardly uses
> any cpu so I’m happy.
>
> You didn’t mention critical regions, so just to be clear; I can
> create a mutex by using the critical enter/exit with thread 0,
> right? Or is this something I have to create my own critical region
> struct for?

Great – I’m glad that was helpful. Yes, you can use the global
critical region (e.g critical_enter(0) and critical_exit(0)). You
could do something more to create your own private mutex if wanted,
but you don’t need to.

best,
Tim


April 11, 2011 | 12:23 pm

Hi

as mentioned before in this thread I took advantage of the possibility to create a thread inside an external.
Thanks for all the information! It works and was really easy to implement.
But I cannot share the experience, that it is not using so much cpu: in my case the cpu in
the activity-monitor of my Mac is going from 3.1 up to 9. The ‘Dsp Status’ keeps its 0 (no audio here).
Is there any improvement in my code?
(Mac Pro 2.5 GHz Core 2 Duo 4 GB Ram 10.6.4)
Thanks in advance for any help!

Best
Johannes

#include "ext.h"
#include "ext_obex.h"
#include "ext_strings.h"
#include "ext_common.h"
#include "ext_systhread.h"

using namespace std;

// a wrapper for cpost() only called for debug builds on Windows
// to see these console posts, run the DbgView program (part of the SysInternals package distributed by Microsoft)
#if defined( NDEBUG ) || defined( MAC_VERSION )
#define DPOST
#else
#define DPOST cpost
#endif

// a macro to mark exported symbols in the code without requiring an external file to define them
#ifdef WIN_VERSION
	// note that this is the required syntax on windows regardless of whether the compiler is msvc or gcc
	#define T_EXPORT __declspec(dllexport)
#else // MAC_VERSION
	// the mac uses the standard gcc syntax, you should also set the -fvisibility=hidden flag to hide the non-marked symbols
	#define T_EXPORT __attribute__((visibility("default")))
#endif

// max object instance data
typedef struct _myclocker {
	t_object			c_box;
	void				*c_outlet;
	t_systhread	 thread;
	short	 thread_running;
} t_myclocker;

// prototypes
void*	myclocker_new(t_symbol *s, long argc, t_atom *argv);
void	myclocker_free(t_myclocker* x);
void	myclocker_assist(t_myclocker *x, void *b, long m, long a, char *s);
void	myclocker_int(t_myclocker *x, long value);

// globals
static t_class	*s_myclocker_class = NULL;

/************************************************************************************/

int T_EXPORT main(void)
{
	t_class	*c = class_new("myclocker",
							(method)myclocker_new,
							(method)myclocker_free,
							sizeof(t_myclocker),
							(method)NULL,
							A_GIMME,
							0);

	common_symbols_init();

	class_addmethod(c, (method)myclocker_int,		"int",			A_LONG,	0);
	class_addmethod(c, (method)myclocker_assist,	"assist",		A_CANT, 0);
	class_addmethod(c, (method)stdinletinfo,	"inletinfo",	A_CANT, 0);

	class_register(_sym_box, c);
	s_myclocker_class = c;

	return 0;
}

/************************************************************************************/
// Object Creation Method

void *myclocker_new(t_symbol *s, long argc, t_atom *argv)
{
	t_myclocker	*x;

	x = (t_myclocker*)object_alloc(s_myclocker_class);
	if (x) {
		x->c_outlet = outlet_new(x, NULL);
	}
	return(x);
}

void myclocker_free(t_myclocker *x)
{
	if(x->thread_running > 0)
		systhread_join(x->thread, NULL);// avoid deadlock if thread should somehow be dead.
}

/************************************************************************************/
// Methods bound to input/inlets

void myclocker_assist(t_myclocker *x, void *b, long msg, long arg, char *dst)
{
	if (msg==1)
		strcpy(dst, "input");
	else if (msg==2)
		strcpy(dst, "output");
}

void myclocker_run(t_myclocker *x){
	long ctn = 0;
	while(1){
		post("hello hell... %ld", ctn);
		systhread_sleep(100);
		ctn++;
		if(x->thread_running == 0){
			systhread_exit(0);
			return;
		}
	}

}

void myclocker_int(t_myclocker *x, long value)
{
	// only trigger a change if values are unequal
	if (x->thread_running != value) {
		if(value == 1) {
			x->thread_running = 1;
			systhread_create((method)myclocker_run, x, 0, 0, 0, &x->thread);
		} else {
			x->thread_running = 0;
		}
	}
}

April 27, 2011 | 5:32 pm

Just a quick comment, but it’s hard for anyone to give you informed feedback without and example patch and object.

However, from the above it looks like you are creating a new thread often for every incoming number (that doesn’t match the last). Creating threads is more expensive than reusing them. And even still for something which is only using 3% CPU multithreading probably isn’t going to give you a noticeable improvement, as multithreading has some CPU costs in thread management and synchronization.

I realize that this is just a minimal example you’re using to learn the API, but if you’re going to test efficiency, you should test with computation heavy work.

There’s also some more advanced use of a pool of threads in the SDK "threadpool" example, and if you search the dev forums on "parallel" you’ll find info on using the ext_parallel.h API, but these might be overkill for your purposes.

Have fun!

-Joshua


December 6, 2012 | 2:35 pm

This is a very interesting topic and because many applications done with max are time-critical due to the focus on realtime capability.
I know that there are many thread programming tutorials out there but I would be interested in some more documented examples with max externals. Especially when it comes to the decision when to create threads and how to organise them.


Viewing 7 posts - 1 through 7 (of 7 total)