Hacker News new | past | comments | ask | show | jobs | submit login

There is no need to use raw pthreads in modern C++, it comes equipped with a much more convenient and capable standard library module.

I prefer working with channels, which are still missing from the library; but they are trivial to add:

https://github.com/andreas-gone-wild/blog/blob/master/diy_cp...




Unfortunately std::thread is not as capable as pthreads. For example, std::thread has no facilities for controlling the stack size or signal mask.

Whenever I use channels, I find I need them to synchronize with each other. For example, I have a Go data structure with a write channel and a separate read channel; how do I avoid a read-after-write hazard?

The simplest solution is to make them the same channel that ships closures, to ensure requests are processed in-order. But then why not just make all channels ship closures, like libdispatch...


Interesting point about the stack size!

As for the signal mask, I'm pretty sure you can just set that using pthreads, and it will apply to the current thread. This is certainly true for the pthreads thread name, which is as deeply as I've investigated myself... the C++ threads library isn't magic. It's just a wrapper round what you'd expect.


You can get the underlying pthread pointer if you want.


Unfortunately pthread attributes need to be set at creation time, so by the time you have a pthread pointer it's too late.


pthread_sigmask always operates on the current thread.


The usual technique is to set the pthread sigmask and then spawn the thread, to avoid the obvious race. But it's risky to assume that `std::thread` won't manipulate the sigmask by itself.


Channels like the one in the link are pretty easy to create. However that's more like Javas BlockingQueue than like Go channels. The power of Gos channels is in "select", which enables a lot more sophisticated synchronization mechansism then just getting data from thread A to B. E.g. waiting for events from multiple sources or cancellation support.

Building channels with select functionality is a lot harder.

An interesting thing is the equivalent of pthreads on windows (WinAPI synchronization primitives) actually provides something which has some similarity with select out of the box: WaitForMultipleEvents, which is also quite powerful. However that is also a still a lot harder to work with than Go's channels, since there are additional sources for errors like HANDLE invalidation, which you can't have in Go where channels are garbage collected references.


What is even more fundamental and useful than select is non-blocking reads (which the implementation I posted supports), once you have that you can write your own select. It's not magic, select has to traverse the list of channels just like any other code. The only reason it feels like magic in Go is that it's the only way to read a channel without blocking.


The non-blocking read parts is also easy, but that's also only one use-case for Go's select. The harder part is the generic "wait until one of the select cases can be taken".

You can't implement this with non-blocking reads alone. If you would, it would be something like busy spinning and iterating through all select cases and trying none-blocking reads until one succeeds. But that's just not efficient.

You need a mechanism instead which also registers a pending select at each event source and unregisters it when done. And the event source (e.g. channel) must wake up one or more select when it's state changes.


Adding a condition-variable per select and keeping a list of pointers in each channel that are pinged when data arrives is the easiest approach I can think of. Haven't tried that one though, I have honestly never run into a use case where I needed select for channels outside of Go.


Select is a kernel level interface. I don't know how it's implemented at the kernel, but there is no technical reason why it can't follow from an interruption to an array index and a file descriptor without ever passing through a list.

Besides, how do you block in user space when there is nothing to read?


Are you saying they are allocating a separate file-descriptor for each channel just to be able to select? I guess you could do that if you wanted to but it's far from the first thing I would try. I remember reading an article on golang about select and having to traverse the list, but I don't really have the motivation to dig deeper right now.

Looping with unblocking reads is plenty fast enough though.

As for waiting on any event without select, adding an optional condition-variable pointer to each channel that is pinged when data arrives is the easiest approach I can think of.


Well, select is well known for not scaling well so AFAIK it may very well use a list. What I'm saying is that kernel code can be much more performant than anything you may create on userland because you have access to the interruptions. And that code would be "magic" in the sense that you really can't replace it with a library.

In fact pinging condition-variables after a channel successfully is read is a much more expensive emulation of that same behavior. I'm curious if the Linux kernel actually exports something like that.


timerfd?


This is good advice and should never have attracted a downvote!

I'm not actually sure that the C++ library does anything that pthreads doesn't - maybe std::lock, perhaps? - but it's more convenient to use (and it's not like pthreads is bad to start with...), and the pieces work well and fit together quite nicely. Some good use of move semantics. unique_lock is neat and you can do stuff like vector<thread>.

The C++ thread library works on Windows, too...


> I'm not actually sure that the C++ library does anything that pthreads doesn't

There’s std::atomic, which didn’t have a standardized equivalent in pthreads. In lieu of that you’d see some ad-hoc combination of GCC builtins, OS-specific functions, and `volatile`, with the result often not guaranteed to work on architectures other than x86 (if at all). Compared to that, std::atomic is both more flexible and easier to get right. The C ‘backport’, stdatomic.h, is also available on most platforms these days...


I was thinking about mostly about atomics, which aren't covered by pthreads; and I can't remember shared mutexes being supported either.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: