On Sun, 30 Oct 2005 14:03:39 +1100 Dave Robillard <[EMAIL PROTECTED]> wrote:
> On Sun, 2005-30-10 at 00:42 +0200, Richard Spindler wrote: > > I read the tutorial at http://userpages.umbc.edu/~berman3/ , it uses > > mutex+condition, is it okay to do this? Are there better ways? > > That's a wonderful tutorial... on how NOT to write a Jack client. > (There's no lock free data structures "in C" or Linux? There's one > _included with Jack_...) > > On mutexes, calling pthread_mutex_trylock in the process thread is okay, > but pthread_mutex_lock is not. Don't Do That(TM). The tutorial client can be fixed pretty easily with this (see below), though as the comment said, the code was written when there didn't exist a lockfree ringbuffer implementation. This has changed (jack provides one). I know that you know this David, just restating the obvious for less experienced readers. Of course the tutorial client still needs to be considered broken even with the trylock. Also the basic problem of signalling (in this case the disk thread that there is work to do) still persists even with lockless ringbuffers. The other thinkable approach would be to make the diskthread wakeup regularly and check whether data is available in the ringbuffer. This is nasty, too, and unsuitable for some situations. So there's basically these approaches nowadays - using named pipes and having the signaller passing single bytes through it to wake up the signallee that blocks on the pipe. This is really not correct, though it might work ok in practice (see jack/ardour (in jack it is used to work around the fact that there's no inter-process mechanism for this though futexes could be used, too, if i understand correctly)). - using condition variables Btw: i think when using a single mutex only for the condition variable (as opposed to the tutorial client which uses the same mutex for the signalling _and_ for synchronizing access to its data structure), the likeliness of contention is rather unprobable given that that the signaller aquires (again, of course via trylock) right before and releases the lock right after the pthread_cond_signal/broadcast. Same for the singallee. As pthread_cond_wait releases the mutex while waiting and reaquires it before handing back control to the signallee the contention case becomes very unprobable when aquiring and releasing the lock right before and after the pthread_cond_wait. Of course with this strategy the case that trylock fails on the condition mutex needs to be handled gracefully (i.e. remembering for the next process callback to try again). Besides the one mutex used only for the condition var, there maybe need to be additional mutexes in this scenario for the shared data structures. Of course from the process callback pthread_mutex_lock is still a nono (again use trylock and handle failure gracefully). But i suppose most locking for the shared data can be worked around with lockless data structures... Btw: in the general case the signallee should always check for spurious wakeups which is not nessecary with the capture_client from the jack distribution as it doesn't hurt to wake up once or twice too often once in a while (at least not when using a ringbuffer - when it's empty the signallee simply goes back to waiting). Maybe the pthread_mutex_lock calls might be moved around the pthread_cond_wait call. The disk thread has the mutex locked all the time during writing. int process (jack_nframes_t nframes, void *arg) { thread_info_t *info = (thread_info_t *) arg; jack_default_audio_sample_t *in; sample_buffer_t *buf; unsigned int i; if (!info->can_process) { return 0; } /* we don't like taking locks, but until we have a lock free ringbuffer written in C, this is what has to be done */ if (pthread_mutex_trylock (&buffer_lock) != 0) { /* this is the unprobable contention case. we will simply do nothing here. audio will be lost. better than an xrun though */ return 0; } /* ok, aquired the mutex, so we can do our thing */ buf = get_free_buffer (nframes, nports); for (i = 0; i < nports; i++) { in = (jack_default_audio_sample_t *) jack_port_get_buffer (ports[i], nframes); memcpy (buf->data[i], in, sizeof (jack_default_audio_sample_t) * nframes); } put_write_buffer (buf); /* tell the disk thread that there is work to do */ pthread_cond_signal (&data_ready); pthread_mutex_unlock (&buffer_lock); return 0; } Have fun and correct me where wrong, Flo -- Palimm Palimm! http://tapas.affenbande.org