Probably I'm very naive here since I'm unused to thread coding.
Do the Tcl/Tk environment need to be thread enabled?
/Mats
static Tcl_Obj *notifyOb = NULLj;
/* From Tcl main thread: */
Tcl_MutexLock(¬ifyRecordMutex);
if (notifyObj) {
Tcl_DecrRefCount(notifyObj);
notifyObj = NULL;
}
notifyObj = Tcl_DuplicateObj(objv[2]);
Tcl_IncrRefCount(notifyObj);
Tcl_MutexUnlock(¬ifyRecordMutex);
>From worker thread created with pthread_create (or _beginthreadex):
Tcl_MutexLock(¬ifyRecordMutex);
if (notifyObj) {
Tcl_Obj *cmdObj = Tcl_DuplicateObj(notifyObj);
Tcl_IncrRefCount(cmdObj);
Tcl_ListObjAppendElement(NULL, cmdObj, Tcl_NewIntObj(...));
...
/* Event posted to the Tcl main thread interpreter event queue
using
Tcl_GetStringFromObj(cmdObj, NULL) and Zoran's code at:
http://wiki.tcl.tk/15342
*/
Tcl_DecrRefCount(cmdObj);
}
Tcl_MutexUnlock(¬ifyRecordMutex);
> Probably I'm very naive here since I'm unused to thread coding.
> Do the Tcl/Tk environment need to be thread enabled?
Is that a trick question? If you're using multiple threads (probably
at all), and ESPECIALLY if you're using Tcl interpreters in multiple
threads, then YES, of course you MUST use a Tcl built with
"--enable-threads". Trying to use a single threaded Tcl for
multi-threaded work is definitely asking for very serious trouble.
> Trying to use a single threaded Tcl for
> multi-threaded work is definitely asking for very serious trouble.
I wouldn't say so. You need thread-enabled Tcl to do any
thready things from within Tcl, but any Tcl works nicely
for scripting within a big multi-threaded program. Each
thread creates its own Tcl interp, and they run well
independently and concurrently.
--
Donald Arseneau as...@triumf.ca
I guess I have asked for serious trouble ;-)
There is only a single Tcl interpreter running on the main thread.
It is just an loadable extension that creates worker threads completely
unrelated
to Tcl, and I construct the callback commands with all arguments
from a worker thread using Tcl_Obj and Tcl_MutexLock etc.
But I guess it is here things go completely wrong since Tcl_MutexLock
defines to empty if the Tcl runtime wasn't built with threads enabled.
Instead I now do:
/* os-dependent macros, etc. From iaxclient. */
#if defined(WIN32) || defined(_WIN32_WCE)
#define MUTEX CRITICAL_SECTION
#define MUTEXINIT(m) InitializeCriticalSection(m)
#define MUTEXLOCK(m) EnterCriticalSection(m)
#define MUTEXUNLOCK(m) LeaveCriticalSection(m)
#define MUTEXDESTROY(m) DeleteCriticalSection(m)
#else
#define MUTEX pthread_mutex_t
#define MUTEXINIT(m) pthread_mutex_init(m, NULL) //TODO: check error
#define MUTEXLOCK(m) pthread_mutex_lock(m)
#define MUTEXUNLOCK(m) pthread_mutex_unlock(m)
#define MUTEXDESTROY(m) pthread_mutex_destroy(m)
#endif
but still is not sure if using Tcl_Obj code from a worker thread is
safe.
Perhaps I should do plain old sprintf(), strcpy(), strcat() instead?
/Mats
Well, I do wonder how much of this is going about stuff the hard way.
> There is only a single Tcl interpreter running on the main thread.
> It is just an loadable extension that creates worker threads completely
> unrelated
> to Tcl, and I construct the callback commands with all arguments
> from a worker thread using Tcl_Obj and Tcl_MutexLock etc.
OK, that's a somewhat supported mode of use. But use of Tcl_Obj
instances from any thread other than the one they're created in is not
supported. I've no idea if you can hack things so that they work;
that's not how Tcl_Objs are designed to be used. (If you had a threaded
Tcl build, you'd definitely be in trouble since we typically use a
per-thread object allocator.)
> But I guess it is here things go completely wrong since Tcl_MutexLock
> defines to empty if the Tcl runtime wasn't built with threads enabled.
> Instead I now do:
[...]
> but still is not sure if using Tcl_Obj code from a worker thread is
> safe.
It isn't at all safe, especially given that everyone is now moving over
towards a default threaded build anyway. Officially, the only safe way
to let an unthreaded Tcl know of some message from another thread is
Tcl_AsyncMark.
> Perhaps I should do plain old sprintf(), strcpy(), strcat() instead?
Sounds like a plan to me.
Donal.
>
> OK, that's a somewhat supported mode of use. But use of Tcl_Obj
> instances from any thread other than the one they're created in is not
> supported. I've no idea if you can hack things so that they work;
> that's not how Tcl_Objs are designed to be used. (If you had a threaded
> Tcl build, you'd definitely be in trouble since we typically use a
> per-thread object allocator.)
>
> > But I guess it is here things go completely wrong since Tcl_MutexLock
> > defines to empty if the Tcl runtime wasn't built with threads enabled.
> > Instead I now do:
> [...]
> > but still is not sure if using Tcl_Obj code from a worker thread is
> > safe.
>
> It isn't at all safe, especially given that everyone is now moving over
I now use Tcl_DString instead to build up the callback script that
is sent to the Tcl interpreter in the main thread.
I've looked at the DString source code and I don't see anything
that is specific to Tcl interpreter or thread. Do you believe this is
OK?
> towards a default threaded build anyway. Officially, the only safe way
> to let an unthreaded Tcl know of some message from another thread is
> Tcl_AsyncMark.
>
It was Zoran Vasiljevic that wrote the part that sends a script to
the main interpreter using Tcl_ThreadQueueEvent() and
Tcl_ThreadAlert().
Seems it is more or less a stripped down from the threads extension.
/Mats
Yes, that looks fine to me. The only thing that might cause a problem
is if the memory allocator is per-thread, and even then only if the
memory is being freed from another thread to the one that allocated it.
(IIRC, threaded Tcl uses a per-thread memory allocator by default these
days, but if you're using an unthreaded Tcl build, that won't apply. If
you've got a threaded build, you'll have to be careful.)
> It was Zoran Vasiljevic that wrote the part that sends a script to
> the main interpreter using Tcl_ThreadQueueEvent() and
> Tcl_ThreadAlert().
> Seems it is more or less a stripped down from the threads extension.
Oh, it was Zoran? He knows exactly what he's doing and can provide
better help than I can.
Donal.
> mat...@gmail.com wrote:
> > I now use Tcl_DString instead to build up the callback script that
> > is sent to the Tcl interpreter in the main thread.
> > I've looked at the DString source code and I don't see anything
> > that is specific to Tcl interpreter or thread. Do you believe this is
> > OK?
>
> Yes, that looks fine to me. The only thing that might cause a problem
> is if the memory allocator is per-thread, and even then only if the
> memory is being freed from another thread to the one that allocated it.
> (IIRC, threaded Tcl uses a per-thread memory allocator by default these
> days, but if you're using an unthreaded Tcl build, that won't apply. If
> you've got a threaded build, you'll have to be careful.)
>
It looks rougly like this:
static void EventLevels(struct iaxc_ev_levels levels)
{
Tcl_DString ds;
Tcl_DStringInit(&ds);
...
Tcl_DStringAppendElement(&ds, buf);
...
XThread_EvalInThread(sMainThreadID, Tcl_DStringValue(&ds), 0);
Tcl_DStringFree(&ds);
}
where the alloc/free is within the same thread (and function)
so I guess I'm safe now, even in a threaded build.
Thanks for all your help to an ignorant thread programmer.
Mats
And isn't that buggy itself? KBK and jenglish have expressed concerns
over its locking usage.
I thought that was threaded builds that had the problem? (Non-threaded
builds can't hit the problem because they don't know how to lock
anything in the first place).
The real problem, as I understand it, is that Tcl_AsyncMark serves
several divergent purposes, some of which really ought to have been
done differently. Firstly, it should set a (C volatile) variable so
that the next time the target interpreter reaches a "safe" point and
calls Tcl_AsyncReady, it will process the defined callback. Secondly,
it may also need to trigger the thread containing the target
interpreter to stop whatever blocking syscall it is doing (i.e., a sort
of gentle interrupt "if it isn't too much trouble") with this second
thing being unnecessary in the truly single-threaded case since the
interrupt will have already been delivered as part of the signal that
caused the call to Tcl_AsyncMark in the first place. There are probably
other things omitted from this list.
Donal (pthreads and signals make my head ache!)
Uhm, not exactly.
You're right about the first: set a volatile variable that
Tcl_AsyncReady can find. But the second "interrupt, please, if
it isn't too much trouble," needs to work even in the single-
threaded case, because of the possibility that Tcl_AsyncMark
was called from a separate thread that *isn't* managed by
Tcl - that's supposed to be acceptable even in the "single
threaded" builds. The only blocking syscall that's really
supposed to be broken by Tcl_AsyncMark, as I understand it,
is the 'select' in the notifier, but I suppose that there are
a few other places where an EINTR would be mostly harmless.
In the multithread case, Tcl_AsyncMark is badly broken, because
it locks a mutex, and entirely lacks any kind of deadlock
protection against running in a signal handler for a thread
that already has the mutex locked. I'm afraid that I don't
have a good fix.
If someone with more urgent needs than mine wants to try
taking over as maintainer of the Unix notifier, I'd be happy
to step down. I signed up for it only after nobody else
wanted it; I don't feel a deep sense of ownership. (Fair
warning: the notifier is rather deep. It's tricky to
understand, not because it's badly commented, but simply because
what it does is subtle and difficult.)
--
73 de ke9tv/2, Kevin
I did say that pthreads and signals make my head ache with good reason!
Donal.