I feel as though 1:1 threading is underrated nowadays. Most of the cited benefits of async or M:N—especially small stacks—are actually properties that can apply equally well to plain old 1:1 threading as well. Linux thread spawning is really fast nowadays.
Yes I agree -- threads without shared state or with thread-safe shared data structures is a perfectly good programming paradigm (and pretty well proven too).
One of my pet ideas is just to implement a Go-like CSP with plain pthreads. Channels could be pipes of pointers. Channel select is just select(). No weird M:N runtime needed. I don't want an operating system in my programming language.
This is just stolen from Programming in Lua chapter 30 -- threads and states (
https://www.lua.org/pil/ , not in the 1st online edition unfortunately)
Lua has coroutines within the interpreter, but to utilize all the cores he suggests layering threads and multiple Lua interpreters on top.
One of my pet ideas is just to implement a Go-like CSP with plain pthreads. Channels could be pipes of pointers. Channel select is just select(). No weird M:N runtime needed.
Please do it! I feel like we've forgotten the lessons of NPTL, when everyone tried M:N and collectively came to a consensus that M:N wasn't worth it in practice.
OK :) I actually have a reasonable project to do it on... I'm implementing a new shell, which is making decent progress, and I've been documenting some interesting things here:
It's very compatible with bash so I think it has a chance of being adopted. And I would like to add structured data pipelines, which powershell has. I was thinking of implementing it with the threads and pipes of pointers scheme (and probably the condition variable).
I guess the shell concurrency model is more like a subset of CSP, but the more general CSP model seems useful and fairly easily implementable. I feel like it should be like 200 lines of code, so I should try it sooner rather than later. Just start porting some simple Go programs to it.
I've just spent 1h at work (silly me) reading your blog, it is illuminating! I love your writing style, and I can't wait to see the your shell in action! Just the full-parse-before-execution is well worth it, especially when deploying scripts on servers.
If you open source the project, I'd love to try giving you a hand. Anyway, good luck with the project!
I'm rounding the corner on parsing hundreds of thousands of lines of bash scripts now... the prototype is in Python as mentioned in the first post, and the executor isn't complete, but if you want the parse-before-execution, that is working well.
ShellCheck does exist though. IIRC I had mixed experience with it -- it did actually find one bug, but on the other hand it spewed hundreds of warnings about double quoting vars, which is technically true, but not the best use of time for most scripts I write. I'd rather just get rid of stupid quoting rules, which is one of the #1 priorities.
(As far as writing, I find that "omit needless words" from Strunk & White goes a long way. Words like "very" and "a little" somehow spray themselves all over my writing; they are rarely useful and I kill them on editing passes :) )
That's probably a good idea. However it will most likely not work out with pipes as I don't know how you could implement the synchronous (unbuffered) channels with them.
IMHO unbuffered channels are one of the most powerful constructs in Go, since they guarantee that "resources" are always on one-side of the channel and are taken care of, and never stored in a channel (or promise, ...) where they could get abondoned/lost. It also allows to make some other assumptions like "the in-memory server has taken my request through a channel and is now working on it and will answer through a chnanel soon" or "the server has shut down so I can't write to the channel", but never "the write to the channel succeeded but nobody cares about it".
Yeah actually the Lua implementation I mentioned uses a condition variable (not sure about the mutex).
The unbuffered channel would be a good reason to use that scheme. I was thinking of using pipes so Go's select reduces to the select() system call. But I think you can just do both -- it's cheap. Write to a pipe and and notify a condition variable. I'll experiment with it.
However from the learning side it doesn't really matter if you have to learn and apply synchronization in a 1:1 threaded or M:N threaded model. It's about the same.
The more important difference is what kind of synchronization primitives are provided to you by the platform. Go's (synchronous) channels in combination with select provide a quite powerful tool compared to using only pthreads. Even the old WinApi with WaitForMultipleObjects and that stuff will result in other ways or solving typical concurrency problems.
Thread spawning is fast but footprints are still pretty large. You'll likely max out around 10k threads with defaults, and maybe tweak it to 100k with small stack sizes.
Last time this came up, it was found that you can get per thread overhead down to 10KB with musl and Linux, and even smaller with upcoming Linux kernel work. That allows for quite a few more than 100,000 threads for typical server RAM configurations.
FWIW, there was an async vs. threads debate at Google that was "resolved" with one very important application running tens of thousands of threads per machine on hundreds of thousands of cores. Some people thought async would be a wiser architecture, but it was made to work (albeit with significant and literally full-stack engineering effort).
The problem of threads vs async in C seems pretty well studied, but a more interesting question is what we're talking about here: concurrency models in higher level languages: async/await in JavaScript vs. threads in JavaScript. Or let's say Python, because it actually has threads.
I feel like that tradeoff has been less well studied. Interpreters probably use a lot more stack space than native programs, but I wonder if anyone has quantified it.
And on the other hand, the downside of async is less pronounced than in C -- the whole point is to avoid "stack ripping" and explicit state machines.
And I have to echo the recent post here about the complexity of the async/await mechanisms in Python, although honestly I'm not that well-versed in the model.
Green threads usually take something on the order of 1kB per thread. That's an order of magnitude more threads per machine, but this is not even the greatest benefit.
Green threads shine because they take ~1kB without tweaking. That means you just pack your software, send elsewhere, and you get those millions of threads per server, instead of losing hours on customer support, and have it revert to 10k threads at random because of bad sysadmins.
Anyway, "thread" is not really a concurrency oriented concept. It mixes so much of parallelism that it's expected that it has some downsides compared o purely concurrent concepts.
It's still an order of magnitude more than many green threads implementations.
musl is interesting though. I've never seen it pitched as a solution for normal machines. I've always seen it in the context of small embedded systems.
web workers are almost entirely useless due to the fact they cant interact with anything except a message channel. Serializing and deserializing everything so web workers can operate on it introduces more overhead than the parallel speedup is worth in the vast majority of cases. Not to mention stuff like DOM updates, web-sockets, or anything else cant be done by them, and are stuck on the main thread.
I suppose if you could only crunch numbers in a Web Worker and then make a clone to get it back to the DOM thread where it would be used, then yeah, Web Workers would be bleh. But, it's not as bad as all that!
That's not true at all, and I expect them to become increasingly important over time. Now that we have the DOM being abstracted away into the virtual DOM in React, Angular 2, Vue, etc., it should be almost entirely possible to run your framework code entirely in a WebWorker while sending back some type of delta/patch to update the actual DOM.
I'm not sure what you mean that web sockets aren't supported since the MDN shows it as being fully available:
Where this falls down is in handling events where some actions inherently need to be synchronous (preventDefault, stopPropagation), however I think Angular 2 was starting to consider options on how to overcome that. I can't speak for progress in other frameworks.
You can do websocket in web workers. At least per spec, and in anything resembling a recent Firefox; it was shipped in Firefox 38 about a year and a half ago. I can't speak for other browsers.
You can also do XMLHttpRequest and fetch in web workers. And IndexedDB, so you can store stuff persistently from a worker and then read it back from a worker later.
I agree that if you want to hand out data on the client to a worker pool after getting all the data from somewhere in the main page script, then serialization/deserialization can start to bite.
They are mostly done in order to free the ui "thread" to avoid getting ui freeze. The event loop is fast enough for most concurrency things in js client applications as long as you divide the tasks into smaller things.
Comments
Ugh. At least when everyone was doing threads you only had to learn one model. Then everyone had to build a better mousetrap.
I feel as though 1:1 threading is underrated nowadays. Most of the cited benefits of async or M:N—especially small stacks—are actually properties that can apply equally well to plain old 1:1 threading as well. Linux thread spawning is really fast nowadays.
Yes I agree -- threads without shared state or with thread-safe shared data structures is a perfectly good programming paradigm (and pretty well proven too).
One of my pet ideas is just to implement a Go-like CSP with plain pthreads. Channels could be pipes of pointers. Channel select is just select(). No weird M:N runtime needed. I don't want an operating system in my programming language.
This is just stolen from Programming in Lua chapter 30 -- threads and states ( https://www.lua.org/pil/ , not in the 1st online edition unfortunately)
Lua has coroutines within the interpreter, but to utilize all the cores he suggests layering threads and multiple Lua interpreters on top.
Please do it! I feel like we've forgotten the lessons of NPTL, when everyone tried M:N and collectively came to a consensus that M:N wasn't worth it in practice.
OK :) I actually have a reasonable project to do it on... I'm implementing a new shell, which is making decent progress, and I've been documenting some interesting things here:
http://www.oilshell.org/blog/
It's very compatible with bash so I think it has a chance of being adopted. And I would like to add structured data pipelines, which powershell has. I was thinking of implementing it with the threads and pipes of pointers scheme (and probably the condition variable).
I guess the shell concurrency model is more like a subset of CSP, but the more general CSP model seems useful and fairly easily implementable. I feel like it should be like 200 lines of code, so I should try it sooner rather than later. Just start porting some simple Go programs to it.
( My last post about parsing expressions got buried on HN but I think it is fairly interesting to a specialized audience: http://www.oilshell.org/blog/2016/11/01.html )
I've just spent 1h at work (silly me) reading your blog, it is illuminating! I love your writing style, and I can't wait to see the your shell in action! Just the full-parse-before-execution is well worth it, especially when deploying scripts on servers.
If you open source the project, I'd love to try giving you a hand. Anyway, good luck with the project!
Great thanks! Yes it will be open source.
I'm rounding the corner on parsing hundreds of thousands of lines of bash scripts now... the prototype is in Python as mentioned in the first post, and the executor isn't complete, but if you want the parse-before-execution, that is working well.
ShellCheck does exist though. IIRC I had mixed experience with it -- it did actually find one bug, but on the other hand it spewed hundreds of warnings about double quoting vars, which is technically true, but not the best use of time for most scripts I write. I'd rather just get rid of stupid quoting rules, which is one of the #1 priorities.
(As far as writing, I find that "omit needless words" from Strunk & White goes a long way. Words like "very" and "a little" somehow spray themselves all over my writing; they are rarely useful and I kill them on editing passes :) )
That's probably a good idea. However it will most likely not work out with pipes as I don't know how you could implement the synchronous (unbuffered) channels with them.
IMHO unbuffered channels are one of the most powerful constructs in Go, since they guarantee that "resources" are always on one-side of the channel and are taken care of, and never stored in a channel (or promise, ...) where they could get abondoned/lost. It also allows to make some other assumptions like "the in-memory server has taken my request through a channel and is now working on it and will answer through a chnanel soon" or "the server has shut down so I can't write to the channel", but never "the write to the channel succeeded but nobody cares about it".
Easy. Just implement them with a mutex and a condvar. If you want, layer some lock free algorithm on top for the fast path.
Yeah actually the Lua implementation I mentioned uses a condition variable (not sure about the mutex).
The unbuffered channel would be a good reason to use that scheme. I was thinking of using pipes so Go's select reduces to the select() system call. But I think you can just do both -- it's cheap. Write to a pipe and and notify a condition variable. I'll experiment with it.
However from the learning side it doesn't really matter if you have to learn and apply synchronization in a 1:1 threaded or M:N threaded model. It's about the same.
The more important difference is what kind of synchronization primitives are provided to you by the platform. Go's (synchronous) channels in combination with select provide a quite powerful tool compared to using only pthreads. Even the old WinApi with WaitForMultipleObjects and that stuff will result in other ways or solving typical concurrency problems.
Thread spawning is fast but footprints are still pretty large. You'll likely max out around 10k threads with defaults, and maybe tweak it to 100k with small stack sizes.
Last time this came up, it was found that you can get per thread overhead down to 10KB with musl and Linux, and even smaller with upcoming Linux kernel work. That allows for quite a few more than 100,000 threads for typical server RAM configurations.
FWIW, there was an async vs. threads debate at Google that was "resolved" with one very important application running tens of thousands of threads per machine on hundreds of thousands of cores. Some people thought async would be a wiser architecture, but it was made to work (albeit with significant and literally full-stack engineering effort).
The problem of threads vs async in C seems pretty well studied, but a more interesting question is what we're talking about here: concurrency models in higher level languages: async/await in JavaScript vs. threads in JavaScript. Or let's say Python, because it actually has threads.
I feel like that tradeoff has been less well studied. Interpreters probably use a lot more stack space than native programs, but I wonder if anyone has quantified it.
And on the other hand, the downside of async is less pronounced than in C -- the whole point is to avoid "stack ripping" and explicit state machines.
And I have to echo the recent post here about the complexity of the async/await mechanisms in Python, although honestly I'm not that well-versed in the model.
https://news.ycombinator.com/item?id=12829759
(Interesting that the top comment there is kind of echoing our issue with M:N threading -- the inner platform effect.)
Green threads usually take something on the order of 1kB per thread. That's an order of magnitude more threads per machine, but this is not even the greatest benefit.
Green threads shine because they take ~1kB without tweaking. That means you just pack your software, send elsewhere, and you get those millions of threads per server, instead of losing hours on customer support, and have it revert to 10k threads at random because of bad sysadmins.
Anyway, "thread" is not really a concurrency oriented concept. It mixes so much of parallelism that it's expected that it has some downsides compared o purely concurrent concepts.
It's still an order of magnitude more than many green threads implementations.
musl is interesting though. I've never seen it pitched as a solution for normal machines. I've always seen it in the context of small embedded systems.
Yeah, but that is a very specific OS stack, which cannot be generalized to language runtimes running on top of general purpose OSes.
Now when targeting bare metal deployments like unikernels, it is a different story in how to approach it.
Threads in JS?
Web workers are threaded.
They are more akin to "processes", because they don't share state with each other or the main event loop.
web workers are almost entirely useless due to the fact they cant interact with anything except a message channel. Serializing and deserializing everything so web workers can operate on it introduces more overhead than the parallel speedup is worth in the vast majority of cases. Not to mention stuff like DOM updates, web-sockets, or anything else cant be done by them, and are stuck on the main thread.
They are really not comparable to threads.
I suppose if you could only crunch numbers in a Web Worker and then make a clone to get it back to the DOM thread where it would be used, then yeah, Web Workers would be bleh. But, it's not as bad as all that!
For example, transferables let you move bulk data between threads without cloning. At least that removes most of the communication overhead. https://developer.mozilla.org/en-US/docs/Web/API/Transferabl...
On firefox you can transfer your WebGL canvas to a Web Worker, how is that for useful? https://hacks.mozilla.org/2016/01/webgl-off-the-main-thread/
You can use Web Sockets in Web Workers in very recent Firefox (48+?) and Chrome versions. https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers... And you've always been able to do AJAXy things on Web Workers as well.
I'm not saying the API's are clean or lovely, but the multithreaded functionality is there for the taking.
38+. It's been shipping for a year and a half.
Off by 10 errors are my specialty!!
Good to know it's stable for awhile, thanks for the update. Go Mozilla, go!!
That's not true at all, and I expect them to become increasingly important over time. Now that we have the DOM being abstracted away into the virtual DOM in React, Angular 2, Vue, etc., it should be almost entirely possible to run your framework code entirely in a WebWorker while sending back some type of delta/patch to update the actual DOM.
I'm not sure what you mean that web sockets aren't supported since the MDN shows it as being fully available:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers...
Where this falls down is in handling events where some actions inherently need to be synchronous (preventDefault, stopPropagation), however I think Angular 2 was starting to consider options on how to overcome that. I can't speak for progress in other frameworks.
You can do websocket in web workers. At least per spec, and in anything resembling a recent Firefox; it was shipped in Firefox 38 about a year and a half ago. I can't speak for other browsers.
You can also do XMLHttpRequest and fetch in web workers. And IndexedDB, so you can store stuff persistently from a worker and then read it back from a worker later.
I agree that if you want to hand out data on the client to a worker pool after getting all the data from somewhere in the main page script, then serialization/deserialization can start to bite.
They are mostly done in order to free the ui "thread" to avoid getting ui freeze. The event loop is fast enough for most concurrency things in js client applications as long as you divide the tasks into smaller things.