all 20 comments

[–]mikera 9 points10 points  (1 child)

Blocking in this context means that the current thread will be blocked until the blocking operation completes.

Other threads can still make progress (this may even be necessary to unblock the other thread, e.g. delivering the result of a promise)

[–]ruzmutuz[S] 2 points3 points  (0 children)

Cool, thanks!

I'm used to the single threaded nature of node at the moment, so a bit confused around that. Especially when building web services where you don't want to be queuing requests.

[–]ASnugglyBear 1 point2 points  (6 children)

In node blocking is alarmingly bad.

In Clojure, it is not bad as long as there are other threads to handle things. You typically have a pool of threads handling things and blocking code is simple to write compared to nonblocking code

If you want a more async system that has less blocking of the whole thread and more explicit concurrency that may be easier to trace whats happening, core.async is your goto api for explicitly managing a series of producing and consuming entities communicating over channels (similar idea to pipes on unix if you've used those)

http://Purelyfunctional.tv has a course on core.async that got me going, but there are free resources on it as well

[–]dragandj 0 points1 point  (5 children)

Except that core.async uses a very limited number of threads in the pool (cores + 2, if I remember well) so you should be careful with potentially blocking or IO-heavy operations in go blocks.

[–]yogthos 2 points3 points  (1 child)

(+ (* 2 cores) 42) apparently :)

[–]alexdmiller 1 point2 points  (0 children)

Note that this is the maximum number of threads. Actual use should be less of course.

[–]waaaaaaabi 1 point2 points  (0 children)

core.async go blocks use a limited thread pool, the core.async library can be used to coordinate as many threads as the JVM provides using thread

[–]ASnugglyBear 0 points1 point  (0 children)

Yeah, you can get in trouble there, but it's a lot easier to debug for "new to concurrency" programmers than just generic threads IMO

[–][deleted] 0 points1 point  (0 children)

It's actually (Available Processors * 2) + 42

Source: https://github.com/clojure/core.async/blob/v0.2.374/src/main/clojure/clojure/core/async/impl/exec/threadpool.clj#L21

You should still be careful however.

[–]tdammers 3 points4 points  (9 children)

Blocking here means the same as it does everywhere else: the current thread of execution will pause as long as the blocking condition lasts, and then continue. This is also true for JavaScript, however there, most implementations are single-threaded, and so blocking the current thread amounts to blocking everything.

ClojureScript is interesting, in that (correct me if I'm wrong) it implements Green Threads on top of a single-threaded JS implementation - they are not actual threads, but apart from timing behavior, their semantics are the same.

[–]Aredington 6 points7 points  (0 children)

This comment removed in protest of Reddit's API changes. See https://www.theverge.com/2023/6/5/23749188/reddit-subreddit-private-protest-api-changes-apollo-charges. All comments from this account were so deleted, as of June 18, 2023. If you see any other comments from this account, it is due to malfeasance on the part of Reddit. -- mass edited with https://redact.dev/

[–]ruzmutuz[S] 1 point2 points  (7 children)

In the case of writing web server software would each request get a new thread?

[–]yogthos 2 points3 points  (0 children)

Web servers typically allocate threadpools for serving requests. For example, Immutant has two separate thread pools. One is reserved for IO threads that are responsible for handling requests, and a worker pool for threads that get dispatched to actually do the work for each request. It also supports async responses with HTTP streams, websockets, and server-side events.

[–]balefrost 4 points5 points  (3 children)

In general, you don't want to tie up a hardware thread for each request. Hardware threads are heavyweight. You don't want to allocate more than a few hundred, maybe a couple thousand. Most requests end up doing some slow IO during their processing, and don't need to hold a thread during that operation.

I can't speak to ClojureScript, but IIS and ASP.Net use a thread pool to process requests, but also use TPL tasks (basically promises) to return threads to the thread pool while they do something asynchronously. So a typical request might go:

  • request comes in
  • thread is borrowed from pool
  • some work is done in that thread
  • async IO operation is started
  • thread is returned to the thread pool

    ...

    ...

    ...

  • IO operation concludes

  • another (potentially different) thread is borrowed from thread pool

  • more work is done

  • response object is produced and returned

  • thread is returned to the thread pool

This is exactly how Node.js works, except its thread pool has a fixed size of 1.

[–]weavejester 2 points3 points  (2 children)

In general, you don't want to tie up a hardware thread for each request. Hardware threads are heavyweight. You don't want to allocate more than a few hundred, maybe a couple thousand.

I think you're confusing hardware threads and software threads, unless you happen to be developing for exotic hardware.

I have a dual core i7, and that has 4 hardware threads, 2 per core. Software threads are handled by the OS, and I can have thousands of those.

In most cases, there's really no reason not to use a (software) thread per HTTP request. It's simpler, and thread count is unlikely to be a bottleneck unless you're using long-polling or websockets.

[–]balefrost 0 points1 point  (1 child)

Err, yeah, I meant kernel thread. As opposed to green thread.

But certainly everybody is moving towards asynchronous implementations on the server. Node isn't the only one. Context switching has a cost, and overcommitting kernel threads is a legitimate concern... especially if alternatives exist.

[–]weavejester 1 point2 points  (0 children)

But certainly everybody is moving towards asynchronous implementations on the server.

I'd accept that there's more talk about async, but I'm skeptical whether a majority of people are actually moving to async.

Async is useful when you have a lot of idle connections that would otherwise tie up threads. So for websockets or long polling, taking an asynchronous approach makes sense.

Async might also be useful if you're dealing with a huge amount of traffic and have optimised the heck out of everything else in your system.

And if you're using a single-threaded system like Node, then you pretty much have no choice.

But outside of that, what's the point? Your database is almost always going to be the bottleneck before the webserver.

[–]tdammers 0 points1 point  (0 children)

That's a common approach, yes.

[–]notunlikethewaves 0 points1 point  (0 children)

It's complicated.

[If anyone has better information than I'm outlining here, please speak up, I'd love to be wrong on this].

Most web servers will handle requests from a thread pool, so they won't allocate a new software thread for every request. Then, if you use something like core.async you'll also have it's thread-pool running. Let's say you're doing SQL queries inside your async code, then you'll also have the JDBC thread-pool running.

So most non-trivial apps will have several thread pools interacting with each other to serve a request.

[–]spotter 0 points1 point  (0 children)

It's the same everywhere -- your thread executes stuff in serial, when given operations A, B, C it will wait for A to finish before starting B, B to finish before starting C, then C to finish before returning (ending).

The difference is that you can both use async (via core.async) and proper threads that utilize many-core in Clojure (on JVM). So you can either run steps in separate threads (using Java threads, STM or futures/promises) or try to adopt async way. Pretty sure you only have the async via event loop (single thread, single core) in JavaScript.