I recently participated in the following programming challenge:
It's essentially an exercise in concurrency: have one piece of logic listen to the keyboard, for the key press, while simultaneously generating a random number for the sleep duration, and finally comparing the event timestamps.
As it turns out, doing it this way is pretty easy:
While it's a bit more verbose, conceptually it is very similar:
Creating a pthread is relatively straight-forward, but instead of using a channel to communicate a result, I had to pass a struct by reference.
The other big difference is that the created pthread has to be joined, to wait on its termination.
This example, with its detailed explanations, was invaluable for wrapping my mind around how pthreads work.
Both the create and join event are fragile, though, and need to be monitored for errors. In this toy program, I just exit if either of those things happen, but it would cause real complexity for a more serious application.
Programming logic aside, there is another distinction with what's happening under the hood.
While pthreads are lighter-weight than full blown operating system processes, they are still much more resource intensive than goroutines. Go's concurrency is based on the Communicating Sequential Processes (CSP) model.
As a result, its goroutines are multiplexed to a small number of operating system threads, instead of using one thread for each goroutine.
But perhaps this is an unfair comparison: C is much older than Go, and pthreads were a later add-on.
Go, in contrast, had the benefit of C's experience, alongside other developments in CS theory and practice.