49

coming from node.js point of view, where all code is non-blocking.

In Go, non-blocking is easily achieved using channels.

if one were writing a node.js type server in go, does it make sense to make it non-blocking? for example, having a database connect() function return a channel, as versus blocking while waiting for the connection to occur.

to me, this seems the correct approach

but ...

John Weldon
  • 39,849
  • 11
  • 94
  • 127
cc young
  • 18,939
  • 31
  • 90
  • 148
  • non-blocking asynchronous IO is better then blocking IO. So yes. – Raynos Jun 13 '11 at 10:27
  • 3
    @raynos: I don't know that's a truism. for example, in a standard php/apache set up, being blocked in one process simply means other processes get their ticks. in node.js non-blocking is imperative because its javascript is single threaded. – cc young Jun 13 '11 at 12:23
  • 3
    @ccyoung in php/apache you create a thread/process for each connection which is inefficient. You _need_ to do this because you block. if you don't block you reduce this overhead. Btw php is also single threaded, apache spawns multiple php processes. Node could have chosen to do this if it wanted to be slow / inefficient. – Raynos Jun 13 '11 at 12:33
  • @Raynos: That's not true at all. In fact I'd say it's preferable to use blocking calls whenever plausible. – Matt Joiner Jun 13 '11 at 12:35
  • 1
    @MattJoiner a blocking call puts a thread to sleep. That's simply a wasted resource. Threads should not sleep they should idle. Besides what are the advantages of blocking IO? – Raynos Jun 13 '11 at 12:41
  • @raynos/@mattjoiner: is this summary ok? 1. in php/apache blocking doesn't matter that much due to the process architecture. 2. single threaded node.js cannot block. 3. I cannot prove that go would run better non-blocking, but at the least it would make better code – cc young Jun 13 '11 at 13:54
  • @ccyoung 3. you've got it the wrong way round. non-blocking io is more performant but blocking IO is easier to write/code. 1. With php/apache you have no choice, you can't use non-blocking IO because apache doesn't support it. 2. javascript is single threaded (so is PHP). You can easily spawn multiple processes in node, you still use non-blocking IO though. Node uses non-blocking IO because it's more performant. – Raynos Jun 13 '11 at 13:58
  • @ramos I think we're in agreement - I probably did not state my case clearly. note my claim that non-blocking is "better code", not simpler code (I can prove this). to verify, is it true that you unequivocally believe that non-blocking is more performant in golang? – cc young Jun 13 '11 at 14:15
  • @Raynos: What do you mean by "idle"? – cdunn2001 Aug 07 '11 at 05:33
  • 2
    @cdunn2001 idle means they are waiting for more tasks. Blocking/sleeping/waiting means they are waiting _for a single task to complete_. Basically idle means it's doing nothing but it can do _anything_ at _any time_. – Raynos Aug 07 '11 at 20:15

5 Answers5

79

Blocking and non-blocking aren't really about performance, they are about an interface. If you have a single thread of execution then a blocking call prevents your program from doing any useful work while it's waiting. But if you have multiple threads of execution a blocking call doesn't really matter because you can just leave that thread blocked and do useful work in another.

In Go, a goroutine is swapped out for another one when it blocks on I/O. The Go runtime uses non-blocking I/O syscalls to avoid the operating system blocking the thread so a different goroutine can be run on it while the first is waiting for it's I/O.

Goroutines are really cheap so writing non-blocking style code is not needed.

dkretz
  • 37,399
  • 13
  • 80
  • 138
Jesse
  • 2,802
  • 20
  • 12
  • thanks. as asked @Nathan, do you think it's a good idea have a db interface's connect() and query() non-blocking? appreciate your insight. – cc young Jun 14 '11 at 06:45
  • 11
    Yes, it's best to make all your functions block the goroutine. This makes them easier to use and think about. The caller can always easily make them non-blocking themselves. – Jesse Jun 15 '11 at 05:01
  • You want a function to block only the code that depends on the result of that function--this should be obvious once you think about it. If you don't need to wait for the result of a function, then stick it in a goroutine. – tylerl Jun 30 '11 at 00:51
  • What makes goroutines cheaper than any other userland threading library? The context switches alone can still cause a major performance hit if they are frequent enough. There is also a non-negligible memory overhead associated with using many threads if you don't know the maximum size of the stack at compile time. – Dan Apr 14 '13 at 22:57
  • Also, from the filesystem's point of view, async I/O can be batched in the kernel and issued in large contiguous writes to disk, which can be a huge performance win because the seek penalty is removed. Synchronous I/O has to be taken care of immediately from the filesystem's perspective (and therefore cannot typically be batched), since something is blocked waiting for it to complete. – Dan Apr 14 '13 at 23:05
  • That is not true because communicating between threads (go routine) comes with a latency which increases with the load of the host. While the code will still work, context switching and synchronization overhead will become a limiting penalty. – chmike Apr 27 '13 at 10:42
  • @gerty3000 you seem to have confused sync filesystem(i.e O_SYNC) operations with sync I/O(read/write) operations. They are very different. – Jesse Aug 11 '13 at 11:56
  • 4
    @chmike The cost of switching goroutines is tiny, similar to the cost of switching events in an event loop. – Jesse Aug 11 '13 at 12:00
  • So I think the confusion is folks thinking a goroutine is a thread or process. It really isnt. Its a coroutine (hence the name goroutine!). Coroutines come from a continuation passing concept where your essentially saving the execution state and switching to a new one without performing a full context switch that you do in switching between threads and/or processes, hence the "heavy vs light" threading. A "light" thread in a coroutine isnt actually a thread, its just thread-like. A goroutine gets to a blocking event and parks its state and not resumed by the runtime until theres IO – Shayne Apr 12 '23 at 03:40
  • The other important thing as well is to remember goroutines are *cooperative* multitasking. They require you do something that will yield the state (Ie a blocking operation, or in general calling well behaved functions). throwing a for {} into your code will do terrible terrible things. So write well behaved code! – Shayne Apr 12 '23 at 03:43
32

Write blocking functions. The language allows you to easily turn a synchronous call into an asynchronous one.

If you want to call a function asynchronously, use a go statement. Something like this:

c := make(chan bool)
go func() {
    blockingFunction()
    c <- true
}()

// do some other stuff here while the blocking function runs

// wait for the blocking function to finish if it hasn't already
<-c
Evan Shaw
  • 23,839
  • 7
  • 70
  • 61
  • 2
    thanks for the example, but my question was not _how_ to get around blocking, but _whether_ writing non-blocking in golang is itself is a worthy goal. sorry if I didn't make that clear ;) – cc young Jun 13 '11 at 11:26
  • 7
    I'm saying that because you can always get around blocking, you should always write blocking functions. – Evan Shaw Jun 13 '11 at 21:52
  • I can respect that. in regard to a db interface, you would not worry about Connect() and Query() blocking, letting the user get around if important to the app. or perhaps out of courtesy the package could offer ConnectNB() and QueryNB(). the package would then need to offer LastErr() as well. – cc young Jun 14 '11 at 06:59
22

In Go, system calls are implemented in a non-blocking way using the most efficient underlying mechanism that the OS supports (e.g. epoll). If you have no other code to run while you wait for the result of a call, then it blocks the thread (for lack of a better thing to do), but if you have alternate goroutines active, then they will run instead.

Callbacks (as you're used to using in js) allow for essentially the same underlying mechanics, but with arguably more mental gymnastics necessary for the programmer.

In Go, your code to run after a function call is specified immediately following the function call rather than defined as a callback. Code that you want to run parallel to an execution path should be wrapped in a goroutine, with communication through channels.

tylerl
  • 30,197
  • 13
  • 80
  • 113
12

For typical web-server type applications, I would recommend not making everything asynchronous. There are a few reasons.

  • It's easier to reason about serial blocking code than async code (easier to see bugs)

  • golang error handling is based on defer(), panic(), and recover(), which probably won't give you what you want with 100% asynchronous code

  • Goroutines can leak if you're not careful [one discussion]. The more async behavior you have, the harder it becomes to track down these types of problems and the more likely they are to show up.

One strategy is to focus the asynchonicity at a high level and leave everything else blocking. So you might have a "database handler" blob that is logically distinct from the "request handler" blob. They both run in separate goroutines and communicate using channels. But within the "database handler", the calls to establish a database connection and execute each query are blocking.

You don't have to choose 100% asynchronous or 0% asynchronous.

Nathan Whitehead
  • 1,942
  • 2
  • 16
  • 19
  • thanks. this and @jessta add a lot. for a db interface, I thought connect() and query() would be good to have non-blocking, for example. is this what you mean by "database handler", or do you mean the the function that, for example, posts to the db? also, thanks for link on leaks. – cc young Jun 14 '11 at 06:43
  • 2
    @cc_young connect() and query() would be blocking in my example and called by the database handler. The database handler would have a channel interface with actions such as "update user nickname", "get friends list". To get the list of friends, you send the username and response channel as a request on the "friend list request" channel. The database handler gets the request, checks the local cache first, returns immediately if cached. Otherwise it executes a query on the database (blocks). Once results are available, send them on the response channel. – Nathan Whitehead Jun 14 '11 at 20:30
  • after working on this several days have concluded you are 100% right. – cc young Jun 18 '11 at 10:20
  • 2
    Saying that the golang error handling is based on defer(), panic() and recover() is not a correct statement. defer() serves many purposes. panic() is very rarely used and should be avoided as much as possible. recover() is an afterthought addition to the language. In Golang, error handling is based on careful error management inside the functions, and an idiomatic return of an error variable that let's the caller decide what to do (ignore it, fix it, or report it to its own caller). – Localist Sep 24 '14 at 08:43
9

Blocking interfaces are always simpler and better than non-blocking ones. The beauty of Go is that it allows you to write concurrent (and parallel) code in a simple, and easy to reason about, blocking style.

The fashion for non-blocking programming is all due to deficiencies in the languages people are using (specially JavaScript), not because non-blocking programming is intrinsically better.

uriel
  • 1,467
  • 13
  • 14
  • 1
    I suspect a degree of inexperience is behind the fashion as well. 99% of the async hype is really a rehash of debates from earlier in computer science where it was generally agreed that blocking on single threaded or heavy (as in computational cost) threads was unadvisable. However the rise of co-routines and green-threads, particularly in functinal languages, back in the 80s and 90s changed that quite a lot as the cost of idle() moved from a computational one to more of a memory cost (Which , I'll add, async doesnt fix) – Shayne Oct 30 '15 at 00:19
  • I agree with @shayne. I just wrote an import script to convert a database. I needed everything to happen one after the other. An Async approach creates more problems than it is worth for this use case. – eDriven_Levar Dec 29 '22 at 15:19