<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Threads on Roberto Selbach</title><link>https://rselbach.com/tags/threads/</link><description>Recent content in Threads on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 02 Mar 2007 20:16:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/threads/index.xml" rel="self" type="application/rss+xml"/><item><title>One Event Loop, One Owner</title><link>https://rselbach.com/one-event-loop-one-owner/</link><pubDate>Fri, 02 Mar 2007 20:16:00 +0000</pubDate><guid>https://rselbach.com/one-event-loop-one-owner/</guid><description>&lt;p&gt;A worker emitted a progress signal today, the label updated twice, and then the application hung while closing. The visible update tempted me to declare the cross-thread code correct. The shutdown was less charitable.&lt;/p&gt;
&lt;p&gt;Qt gives each &lt;code&gt;QObject&lt;/code&gt; thread affinity. Queued events for that object are processed by the event loop in its owning thread. A signal connected across threads normally becomes a queued call, so the receiver&amp;rsquo;s slot runs where the receiver lives. This permits a worker to report results without directly touching GUI objects.&lt;/p&gt;
&lt;p&gt;The safe shape is simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;connect(worker, SIGNAL(progress(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dialog, SLOT(setProgress(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Qt&lt;span style="color:#f92672"&gt;::&lt;/span&gt;QueuedConnection);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The explicit connection type documents the boundary. The worker emits an integer; the dialog owns the label and updates it on the GUI thread.&lt;/p&gt;
&lt;p&gt;My hang came from destruction. The dialog waited for the worker while handling its close event, and the worker was waiting for a queued reply from the dialog&amp;rsquo;s event loop. The GUI event loop could not deliver the reply because it was blocked waiting. Both sides were admirably patient.&lt;/p&gt;
&lt;p&gt;I removed the synchronous callback. Closing now requests cancellation, disables the interface, and lets normal event processing continue until the worker announces completion. Only then is it destroyed. Data passed in signals is copied as needed, so custom value types must be registered if they cross a queued connection.&lt;/p&gt;
&lt;p&gt;I prefer message-like queued signals between worker and GUI code. They make ownership visible and avoid shared widget state. The caveat is that queued delivery is asynchronous: the sender cannot assume the slot has run, event loops must remain active, and ordering guarantees do not replace a proper state machine.&lt;/p&gt;
&lt;p&gt;Nested event loops are another temptation. Opening a modal operation or manually calling event processing can keep paints moving, but it also permits unrelated events to re-enter code whose invariants are half changed. I use that technique only with a very clear boundary, never as a general cure for long work.&lt;/p&gt;
&lt;p&gt;The practical test now closes the window at several stages of the job. Starting and finishing are easy; cancellation and teardown reveal who actually owns what. If a design works only while the user behaves, the user is not the unreliable component.&lt;/p&gt;</description></item><item><title>Const Does Not Mean Concurrent</title><link>https://rselbach.com/const-does-not-mean-concurrent/</link><pubDate>Thu, 21 Sep 2006 19:26:00 +0000</pubDate><guid>https://rselbach.com/const-does-not-mean-concurrent/</guid><description>&lt;p&gt;A cache lookup crashed under load today even though every worker called a &lt;code&gt;const&lt;/code&gt; method. That sentence sounds reassuring only if &lt;code&gt;const&lt;/code&gt; is mistaken for a locking primitive.&lt;/p&gt;
&lt;p&gt;The method updated a mutable cache:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QImage ImageStore&lt;span style="color:#f92672"&gt;::&lt;/span&gt;image(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;key) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;m_cache.contains(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_cache.insert(key, loadImage(key));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;const&lt;/code&gt; prevents ordinary modification through the public type. It says nothing about simultaneous calls, and &lt;code&gt;mutable&lt;/code&gt; explicitly allows internal state to change. Two processors can both inspect and modify the cache, corrupting its internal bookkeeping.&lt;/p&gt;
&lt;p&gt;I first added a mutex around the whole operation. That was correct but serialised disk loading, so unrelated images waited behind one slow file. The improved version checks under lock, loads outside it, then locks again to publish:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QImage ImageStore&lt;span style="color:#f92672"&gt;::&lt;/span&gt;image(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;key) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.lock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (m_cache.contains(key)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QImage image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; image;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QImage image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; loadImage(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.lock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;m_cache.contains(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_cache.insert(key, image);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; image;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Duplicate loading is possible, but publication is safe and the common cached path is short. For expensive loads I might track in-progress keys with a wait condition; that complexity needs measurements first.&lt;/p&gt;
&lt;p&gt;Implicitly shared Qt values make returning an image cheap in the usual case, but they do not make shared storage safe for concurrent mutation. Copy-on-write protects value semantics, not arbitrary access patterns.&lt;/p&gt;
&lt;p&gt;The lock itself belongs beside the state it protects. Passing the cache to helper code without the mutex invites a second access path that quietly ignores the rule. I am documenting the invariant as &amp;ldquo;all cache lookup and publication occurs while holding &lt;code&gt;m_mutex&lt;/code&gt;,&amp;rdquo; then checking each method against that sentence.&lt;/p&gt;
&lt;p&gt;I prefer immutable inputs and per-job results when using several cores. Shared caches should be small, explicitly locked, and treated as an optimisation rather than the foundation of correctness. The caveat is memory and copying: some data is too large to duplicate, so careful shared ownership remains necessary.&lt;/p&gt;
&lt;p&gt;The failure appeared only on the dual-core machine and only after several minutes. That does not make it a rare bug. It makes the single-core test machine an excellent accomplice.&lt;/p&gt;</description></item><item><title>ThreadWeaver and the Size of a Job</title><link>https://rselbach.com/threadweaver-and-the-size-of-a-job/</link><pubDate>Wed, 14 Jun 2006 18:54:00 +0000</pubDate><guid>https://rselbach.com/threadweaver-and-the-size-of-a-job/</guid><description>&lt;p&gt;I fed 12,000 thumbnail jobs into a ThreadWeaver experiment today. The queue accepted them with admirable composure. The application then spent more time managing tiny jobs than producing thumbnails.&lt;/p&gt;
&lt;p&gt;The mechanism is straightforward: jobs enter a queue, worker threads take runnable jobs, and completion is reported back. This is much better than creating one thread per thumbnail. Threads have stacks, scheduling cost, and an unfortunate tendency to outlive the code that launched them.&lt;/p&gt;
&lt;p&gt;My mistake was choosing a job that was too small. Each item performed a little path work, checked a cache, and often returned without decoding anything. Queue locking and signal delivery became a noticeable fraction of total work.&lt;/p&gt;
&lt;p&gt;Batching nearby items helped:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ThumbnailBatch&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ThreadWeaver&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ThumbnailBatch(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QStringList &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; m_paths(paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; run()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foreach (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;path, m_paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; createThumbnail(path);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QStringList m_paths;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The batch is large enough to amortise scheduling but small enough that several workers remain busy. There is no universal number. Disk latency, decoding cost, and cache hit rate all change the useful grain size, so I am measuring rather than engraving 32 on a tablet.&lt;/p&gt;
&lt;p&gt;Cancellation also becomes a design issue. Removing queued jobs is easy; stopping one already decoding is cooperative. Long operations need checkpoints, and the job must own or safely reference everything it touches. A window closing while a worker holds its raw pointer is not cancellation. It is delayed excitement.&lt;/p&gt;
&lt;p&gt;I prefer a shared job queue such as ThreadWeaver for independent background work because it bounds concurrency and centralises scheduling. The caveat is that it does not make unsafe code safe. Shared caches still need locking, GUI objects still belong to the GUI thread, and dependencies between jobs still need explicit representation.&lt;/p&gt;
&lt;p&gt;Priority needs restraint as well. Marking every visible thumbnail urgent merely recreates a first-in queue with more bookkeeping. I reserve priority for work whose delay is observable now, and still ensure ordinary jobs eventually run.&lt;/p&gt;
&lt;p&gt;The current prototype sends immutable input into a job and returns a completed image through a queued signal. That copy may cost something, but the ownership story fits in one sentence. For now, clarity beats shaving a copy whose cost I have not measured.&lt;/p&gt;</description></item><item><title>Threads in Userspace and the Kernel</title><link>https://rselbach.com/threads-in-userspace-and-the-kernel/</link><pubDate>Thu, 18 Mar 2004 17:50:00 +0000</pubDate><guid>https://rselbach.com/threads-in-userspace-and-the-kernel/</guid><description>&lt;p&gt;A program I was debugging had six POSIX threads, &lt;code&gt;ps&lt;/code&gt; showed one process, and &lt;code&gt;/proc&lt;/code&gt; showed several task directories. This produced the inevitable question: are Linux threads implemented in userspace or in the kernel? The unhelpful but accurate answer is both.&lt;/p&gt;
&lt;p&gt;The pthread library provides the interface the application sees: &lt;code&gt;pthread_create&lt;/code&gt;, mutexes, condition variables, thread-local state, and POSIX rules. With NPTL, creating a thread eventually uses the kernel&amp;rsquo;s &lt;code&gt;clone()&lt;/code&gt; facility to create another schedulable task that shares selected resources with its siblings.&lt;/p&gt;
&lt;p&gt;The kernel does not need a completely different object named &amp;ldquo;thread.&amp;rdquo; It schedules tasks. Flags passed to &lt;code&gt;clone()&lt;/code&gt; determine whether tasks share an address space, file descriptor table, signal handlers, and other state. Tasks in the same thread group form what userspace presents as one process.&lt;/p&gt;
&lt;p&gt;This is visible under procfs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls /proc/&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;pidof myprogram&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;/task
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each entry is a task ID. The main thread&amp;rsquo;s task ID is also the process ID normally shown by tools. Updated &lt;code&gt;ps&lt;/code&gt; can display either the process-oriented view or individual threads, depending on its options. Neither view is lying; they answer different questions.&lt;/p&gt;
&lt;p&gt;Synchronization also crosses the boundary selectively. An uncontended NPTL mutex is normally handled with atomic operations in userspace. Entering the kernel for every lock and unlock would be expensive. When a thread cannot acquire the lock, a futex operation lets it sleep in the kernel; an unlock can wake a waiter when necessary.&lt;/p&gt;
&lt;p&gt;This &amp;ldquo;fast userspace, kernel on contention&amp;rdquo; pattern explains why futex means fast userspace mutex. It does not mean the kernel knows nothing about waiting threads. The kernel supplies the queueing and scheduling that userspace cannot safely improvise.&lt;/p&gt;
&lt;p&gt;Signal handling is where abstractions become less tidy. Some signals target the process or thread group, while others target a particular task. The library and kernel cooperate to provide POSIX behaviour. Code should use pthread signal operations instead of guessing that numeric task IDs are interchangeable with old Linux process IDs.&lt;/p&gt;
&lt;p&gt;The same warning applies to debugging. If one thread blocks, inspect the task view; if the question concerns resource ownership, remember that file descriptors and memory may be shared. Killing a random task because it looks like a child process is not thread debugging. It is experimental program termination.&lt;/p&gt;
&lt;p&gt;I find the model easier once I stop asking whether a thread is &amp;ldquo;really&amp;rdquo; a process. In Linux it is a schedulable task with a particular set of shared resources, grouped so userspace can provide process and pthread semantics. That is a mechanism, not a philosophical position, and it matches what the tools are showing.&lt;/p&gt;</description></item><item><title>NPTL and the Disappearing Processes</title><link>https://rselbach.com/nptl-and-the-disappearing-processes/</link><pubDate>Tue, 14 Oct 2003 20:05:00 +0000</pubDate><guid>https://rselbach.com/nptl-and-the-disappearing-processes/</guid><description>&lt;p&gt;I upgraded a test machine to a recent glibc with NPTL and immediately thought several processes had disappeared. An old LinuxThreads program used to make &lt;code&gt;ps&lt;/code&gt; look as if every thread had invited itself to the process list. With NPTL and updated tools, the display is much closer to what the programmer meant: one process containing several threads.&lt;/p&gt;
&lt;p&gt;The old implementation was clever, but its compromises leaked. LinuxThreads created tasks with &lt;code&gt;clone()&lt;/code&gt; and used a manager thread for coordination. Thread IDs and process IDs did not line up neatly with POSIX expectations, signal delivery had awkward corners, and tools often presented threads as peculiar sibling processes.&lt;/p&gt;
&lt;p&gt;NPTL also builds threads on kernel tasks, but newer kernel facilities let it implement POSIX semantics much more directly. Thread groups identify tasks belonging to one process. Futexes provide a fast synchronization primitive: uncontended locking can happen in userspace, while the kernel becomes involved when a thread must sleep or wake another waiter. That avoids a system call for every successful mutex operation.&lt;/p&gt;
&lt;p&gt;The practical lesson is to upgrade the tools with the library. A stale &lt;code&gt;ps&lt;/code&gt;, debugger, or monitoring script can report technically available data in a thoroughly unhelpful way. On this machine I check both the process view and the individual tasks under &lt;code&gt;/proc/PID/task&lt;/code&gt; when debugging:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls /proc/1234/task
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each directory there is a kernel task ID. The process has a thread-group leader, and the other tasks share resources according to the flags used when they were created. &amp;ldquo;Thread&amp;rdquo; is not a completely separate kernel creature. It is a task sharing an address space and other state with its group.&lt;/p&gt;
&lt;p&gt;Compatibility deserves some attention too. Programs should use the POSIX thread API and not depend on LinuxThreads accidents such as each thread appearing to have an unrelated process identity. Code that sends signals to guessed numeric IDs is especially suspicious. Correct code generally needs no source change, but incorrect assumptions have enjoyed years to become tradition.&lt;/p&gt;
&lt;p&gt;NPTL is faster in useful places, particularly thread creation and synchronization, but I like the semantic cleanup more. Better benchmarks are welcome; fewer explanations beginning with &amp;ldquo;well, on Linux a thread looks like&amp;hellip;&amp;rdquo; are better. The implementation is finally making the common abstraction less dishonest.&lt;/p&gt;</description></item></channel></rss>