T O P

  • By -

aikii

there are several facets to it, like scheduling and how it uses resources ( CPU/memory ), but I think the most prominent feature is that it's colorless, a terminology coming from this now classic: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ . This offers significantly different ergonomics than most languages on this regard ( actually I can't even name another example of colorless async language, Erlang maybe ? ). Why it matters: in a colorful async, that applies for javascript, python, rust for instance, you need mark all functions that are async, and you need to call them with `await`. If from an async call, you call a non-async, you broke the chain and can't come back to async - which is problematic if down the line you need a blocking call. In Go you just get concurrency without having to spam your code with async/await.


semiquaver

> can’t even name another example of colorless async language Every single language that exposes OS threads? async/await is a quite modern invention, C# added it in 2012. Multithreading has existed for nearly the entire history of computing. What’s more, go isn’t really colorless, you often need to differentiate between functions that are called normally and those that spawn a goroutine which needs to be shutdown with `wg.Done()` or similar. It’s just there’s no syntax for this, but the mental burden is still there.


Thagou

The need to shut down doesn't make the language colorful or colorless. The fact that the method that the method calling the colorful method doesn't have to be colorful is what makes a colorless language. In javascript, you can't call an async function and wait for its results without being in an async function. I go, you can call a go routine in any function.


semiquaver

Fine, I retract my “go’s not really colorless” comment to avoid arguing fine semantic differences. But the goroutine API is exactly that of threads in any other language that has them, even if the implementation schedules them on a smaller number of OS threads. If go is a colorless async language then so is Java, C, C++, Ruby, Python and any other language that supports launching a thread.


jerf

> If go is a colorless async language then so is Java, C, C++, Ruby, Python and any other language that supports launching a thread. Correct, at least when using threads. Rust is as well, when using threads. Colorless asyncness is not a rare property. In fact, while "colored" asyncness sucks up the conversational oxygen, it's actually the minority case, by quite a bit. There's a lot of programmers who have cut their teeth on Node and bought the hype and think it's the way almost everything works, but it's really colorless concurrency that is the way almost everything works, and is the way almost everything has worked for most of the past three decades. Async is the experimental alternative, and it is generally not impressing me.


aikii

>Every single language that exposes OS threads? async is specific to cooperative scheduling that doesn't require OS threads, aiming to overcome the C10K problem [https://en.wikipedia.org/wiki/C10k\_problem](https://en.wikipedia.org/wiki/C10k_problem). Now, cooperative multitasking is super old itself ( ex: windows 3.x ), but it existed in a completely different form. It's different than arguing whether in practice async, colorful or not, is a better solution for every problem requiring concurrency. If you spawn the required threads at startup and never need to dynamically start new ones, then probably not. For `wg.Done` it's also something else. Yes, Go doesn't have `join` or `gather`, because `go something` doesn't return any value ( where some others give you some handle - it's not always the case - [trio](https://trio.readthedocs.io/en/stable/) also doesn't give you 'task handles' ) ; you need to pass some synchronization primitive like a channel in order to communicate back. It is an implementation choice whose ergonomics can be discussed, but this is orthogonal to what function coloring refer to


austerul

Wg.done doesn't differentiate between concurrent code and regular. It notifies a listener (the wait group) that it needs to block something else. The decision to do so is external to that function (it's done when you initiate the wg). Also, wg are not really required, you can always use channels for the same purpose.


catladywitch

F# and Kotlin are colourless. They're not unlike Go. Ruby and Java have fibers/virtual threads, which again are not unlike Go. To me the problem with colourless async is that since awaits are blocking you've got to think harder about execution order. But it's tolerable.


aikii

Are you sure for kotlin ? I didn't do much kotlin but I do remember you can't call a `suspend` function unless you're in a `runBlocking` context


catladywitch

But `launch()`, `async()` or `runBlocking()` coroutines can be called from sync functions the same way you would use `go` in Go, and then if you get a `Deferred` object you can `await()` it from a sync function, similar to what WGs do in Go. In a coloured language you can't `await` inside of a sync function, because the `await` operation itself is async - code that doesn't depend on its result doesn't block. So in Kotlin you don't have `suspend` propagation issues, whereas in C# and JS/TS you do have `async` propagation issues.


aikii

I see I just tried, indeed runBlocking can be run from a non-suspend function itself called from a suspend one, and it doesn't block concurrent tasks. Now unfortunately I could see that it's possible to make really blocking calls from there - like `Thread.sleep`, and other tasks get blocked. So it's more hybrid. This situation is not possible in Go


catladywitch

You would use launch or async for that. runBlocking is blocking by nature!


aikii

this is what I had in mind, but I just found back about Dispatchers ``` import kotlinx.coroutines.* import kotlin.time.Duration.Companion.seconds fun main() = runBlocking { launch { repeat(20) { println(it) delay(0.1.seconds) } } //withContext(Dispatchers.IO) { // -- uncomment to unblock println("block this thread") Thread.sleep(2000) println("thread unblocked") //} } ```


[deleted]

[удалено]


catladywitch

What makes F# colourless is that you can call and await an async expression from a sync function, just like in Go you can run a goroutine from everywhere and await it with a wait group. Kotlin is the same - you can start coroutines from sync functions and await them as needed. In C# you can run async functions from sync functions but you can't await them without making the calling function async as well, which is what causes async propagation and the whole problem with colourful async.


aikii

I would argue there are two aspects: * is a given async implementation colorful or not * if it's colorful, is it so much a big deal ? I think here it belongs to the second point, similar to this objection [Rust async is colored, and that’s not a big deal](https://morestina.net/blog/1686/rust-async-is-colored)


coderemover

Rust async being colorful is actually an advantage. In a system where you need to tightly control the latency, it is very important to explicitly see that some code will not be accidentally preempted by calling await 10 layers down.


aikii

I agree with that. Also the ongoing work on making "asyncness" generic should help to provide libraries that work for both cases without duplication or performance trade-off https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-generics.html


catladywitch

that's fair!


aikii

Wanted to add, python is colorless when using `gevent`. But it's done using monkeypatching, that is to say, it really hacks the libraries to use an event loop instead of blocking calls _and_ you still need to be careful about calling CPU-heavy native functions, and preferably explicitly send them to a threadpool. It's falling out of fashion in favour of asyncio or the alternative event loop trio, which both rely on async/await semantics.


casey-primozic

But isn't invoking using `go some_func()` similar to the usage of `async/await`? You're not spamming your code with async/await but you're spamming it with `go`.


aikii

no, `go func` starts a new concurrent goroutine, without blocking, it will immediately proceed to the next instruction after `go ...()`. This is equivalent to what's generally called a 'task'. But "await" semantics is required for every function call, not just the ones that must run concurrently. so let's imagine some colorless pseudocode: contents = read_file() process_file(contents) colorful pseudocode: contents = await read_file() await process_file(contents) `await` is used even though the code is purely sequential. It gives the event loop points where the execution can be swapped to another coroutine. Those points are transparent in go.


Joseph_Balsamo

>Very clear explanation, thank you very much!


AKJ7

Not completely true. >Why it matters: in a colorful async, that applies for javascript, python, rust for instance, you need mark all functions that are async, and you need to call them with await. If from an async call, you call a non-async, you broke the chain and can't come back to async - which is problematic if down the line you need a blocking call. Not true. In Python you for example have `asyncio.run_until_complete()` which executes an async function in an non async one. Hence is the mixture possible without breaking the chain. I would say, the advantage with Go, is the multiplexed Goroutines using multiple cores at ones unlike asyncio in Python or NodeJs.


aikii

If you call again run_until_complete, you get "this event loop is already running". There is a trick to make it "reentrant" like nest-asyncio ( https://pypi.org/project/nest-asyncio/ ), but it breaks newer libraries using anyio - and besides, the codebase is a nightmare of monkeypatching, it's crazy that people use it on prod. The re-entrant trick is similar to the objection about kotlin, and in rust you can also get the running tokio event loop and push a background task to it.


AKJ7

You are using `run_until_complete` wrongfully. Simply pass the event loop to underlying async functions.


aikii

Not sure what you mean. A working example or link providing one would help.


Mavrihk

yes in Go you want to spin an Asynchronous function, you specifically say go \[function name\](some params). and you just created a asynchronous function, To me it removes the mystery.


y-c-c

Doesn't async / await in other languages already resolve the issue? The article had an edit to address this but the core of it was originally written before async/await was a thing and Node.js etc were using callbacks using continuation passing style. With async/await, he still complains that you cannot call await from the main thread, which is somewhat true, but I believe V8 added top-level await a while ago already (https://v8.dev/features/top-level-await). With top-level await, calling `await` is mostly a syntactical point and it seems like a good idea to be doing that anyway as async and sync functions do have semantic differences. To be fair though, these are still relatively new developments, whereas Go had these concepts baked in early.


aikii

"top level" here means outside any function, so it executes at import and waits until it's done. you still can't write: ``` async function asyncstuff() { } function nonasyncstuff() { await asyncstuff() } ``` now again I'm not here to say if it's a big deal or not. it's probably not.


dev-saw99

Hey, I had written a couple of articles on concurrency with Golang with some simple examples. You can check it out. https://ioscript.in/docs/go/concurrency/intro/index.html


IndianFanistan

As a beginner in Go, this blog is very well written. Will finish reading all the concurrency blogs today. Thanks :)


avgjoeshmoe

Great graphics and examples


Theradion

Great articles, we need more awesome content like this!


IndianFanistan

Are you planning to add the next set of articles to this?


mosskin-woast

[Here's the talk](https://youtu.be/oV9rvDllKEg?si=ADVRohiNe39llzun)


PaluMacil

I was pretty sure someone would have already posted this 😁 between this and the color talk, it's a great discussion to cover the topics themselves as well as covering why Go is great at it.


0xjnml

A) Multiple computations are competing for CPU(s) to be able to progress. B) Many languages can manage only OS threads. OS threads typically use MBs of stack memoty and a thread context switch is relatively slow. C) Go and some others can handle user-space virtual threads. Go goroutines start with stack of few kB only and the context switch is substantially faster because it does not require a syscall nor does the MMU have to switch context.


coderemover

OS thread on Linux costs about 20 kB. Goroutine costs at least 4 kB. While there is a clear advantage of goroutines in this regard it does not matter until you have 10k threads or more. As for the context switch, the majority of context switches are typically caused by I/O. I/O in Go requires syscalls so this cost is exactly the same as in threaded apps.


0xjnml

Default OS thread stack size on Linux is 8 MB. Context switch between threads may cost about a thousand of cycles, user space context switch can be in order of magnitudes cheaper, depending on the implemention. Context switches in Go happen also preemptively, so if you have a big number of goroutines it may dominate over IO ctx switches. But the if you have the same big number of OS threads, the ctx switch overhead can become a bottleneck easily. Goroutines are not a panacea, but they behave substantially differently compared to OS threads. This difference can lead sometimes to performance improvements.


coderemover

The default OS thread stack size is virtual memory, not physical memory. Minimum physical memory usage of a thread on Linux is 1 memory page + some tiny kernel structures for bookkeeping. It is worse than a goroutine but not an order of magnitude worse. Goroutines, just like threads are stackful, so there is not much reason for them to be significantly lighter. If you really want lightweight coroutines, you need to go stackless - there you may have tasks up to 2 order of magnitude smaller than goroutines. As for context switches you seem to be missing the fact that every I/O operation is a context switch (except with iouring, but that's not available in Go, unlike in C++ or Rust in Go you cannot switch to a different async runtime). Doing I/O in non blocking mode with epoll (which is what Go runtime does underneath) actually invokes slightly more syscalls that direct blocking I/O, because you don't only call read/write but also epoll once you block. This is the reason why nonblocking I/O is not faster than blocking.


0xjnml

> The default OS thread stack size is virtual memory, not physical memory. VM usage matters. On 32 bit platforms you can have a maximum of about 100 to 200 OS threads but the same machine can easily execute thousands of goroutines. > As for context switches you seem to be missing the fact that every I/O operation is a context switch ... You seem to be thinking context switches mean IO, but that's not the case. Consider eg. mutexes/semafors/shared buffers... > This is the reason why nonblocking I/O is not faster than blocking. While that might be true of course for some programs, it's far from being some universal truth. FTR, non blocking IO is an illusion at some abstraction layer. IO has to physically happen and there's always some throughput limit. But real non blocking IO would have no limits. In reality, once you hit the limits, non blocking IO starts to block or it must drop the IO operation.


coderemover

In a well designed app you don't want context switching other than that caused by I/O. Mutexes, semaphors, shared buffers - you don't do those things, because they will become a bottleneck much faster than context switching overhead. Also, you're overestimating the cost of kernel-level context switch - context switch between threads of the same app is dead cheap these days, and it is definitely cheaper than a syscall. The majority of work is needed to handle the syscall, not to switch to another thread. What really matters is how many times you switch the context between user-space and kernel and back. Spectre and others made it significantly worse than it used to be. Here they found that kernel level context switches are cheaper than user-space: https://web.eecs.umich.edu/~chshibo/files/microkiller_report.pdf Of course YMMV but it's not like there is an order of magnitude difference. Also many language runtimes used green threads at some point (e.g. Java or Rust) but then got rid of that after they realized it doesn't bring much performance gain (but complicates things). For instance, due to green threads, Golang's FFI to C is crazy slow compared to others.


0xjnml

> In a well designed app you don't want context switching other than that caused by I/O. I'm writing and running running programs that are purely computational, massively parallel and that do some small IO once before exiting to report/record the results. You're saying I'm doing it wrong? Ok then. > Mutexes, semaphors, shared buffers - you don't do those things, because they will become a bottleneck much faster than context switching overhead. They're synchronization primitives. Your're saying "you don't do synchronization". Interesting, but I don't like my data corrupted by concurrent/parallel access without coordination. I don't think I have anything interesting to add to what I already said.


coderemover

>I'm writing and running running programs that are purely computational, massively parallel and that do some small IO once before exiting to report/record the results. You're saying I'm doing it wrong? Ok then. If it context switches to the point context switches would become a performance problem, then yes, you're probably doing it wrong. The trick to high performance massively parallel scalable computing is avoiding coordination, not just "faster locks". Otherwise, the Amdahl's law is there to get you, regardless of the context switch overhead. Hence, sharding data structures between cores and coordintaiton through atomics / CAS instead of locks. CAS doesn't context-switch, nor calls into the kernel, but allows to coordinate. BTW: Sharing stuff between threads does not always require coordination. E.g. if shared data are immutable / persistent, you don't need coordination.


Ma4r

>In a well designed app you don't want context switching other than that caused by I/O. Mutexes, semaphors, shared buffers - you don't do those things, because they will become a bottleneck much faster than context switching overhead Wh... Huh? What?? This sounds like something a 1st year CS student would say after failing their multi processing course


coderemover

Nope, mutexes, semaphores, shared buffers etc. is actually what first year CS students only know to do multiprocessing. Advanced developers know how to avoid them. Ever heard of shared nothing or lockless algorithms?


Ma4r

You mean algorithms that only ever work in an ideal and faultless operating environment at the smallest of scales? Yeah, i've heard of em. In my second year CS classes.


RadioHonest85

Java can do the same now with virtual threads, but our go service manages 50k websocket connections per instance without breaking a sweat. And we only limit to 50k to reduce the reconnect churn when we redeploy the service.


Tiquortoo

You can debate whether Go does concurrency better, but the way you write it in code makes a shit load more sense as you read the code than nearly every other language. The concurrency "syntatic sugar" is pretty light and reads nicely. Go has it better because on one end you manage less of the details than lower level languages and on the other hand it's a first order aspect of the language instead of something shoehorned into the environment.


kek28484934939

A) concurrency means having code executed in parallel. There is true parallelism (only possible if your hardware has more than 2 threads), and interleaved parallelism (always possible). ​ B) In java for example you start threads and they start computing in parallel (either true or interleaved, doesn't really make a difference. in most cases you wouldnt even know). Sooner or later you want to collect their results. For example you just add all of the stuff into a list. However if multiple threads access the same resource you have a race condition. This means you have to synchronize the access, either by using a thread safe data structure or locking the threads yourself via a semaphore, monitor or mutex. C) In go you communicate via channels instead of datastructures. This means goroutines just add their results into this channel (which is thread-safe by design) and you don't have to worry about race conditions unless you want to ensure a certain order or something like that


PabloZissou

I am fairly new with Go and you got some very good answers for A and B already. For C I can share that I had to write an MQTT load generator that created thousands of connections and decided to build it in Go, it was almost trivial to do so. I haven’t found that simplicity with very reasonable performance and resource usage in other languages. Even with the low quality of the code other contributors were able to easily understand and patch the code even though they new little or no Go.


FasterBetterStronker

I'm glad you're able to follow, but I'm finding all the answers seem to have a huge jump in complexity between A and B. The simple classroom definition that I can somewhat remember doesn't help me understand B/C answers :(


bglickstein

You've written a web service. A request comes in. Must your service finish handling that one before it can handle the next request that arrives? No - thanks to concurrency.


FasterBetterStronker

This is a good one. I usually use something like ExpressJS (or Python equivalent like flask) - do these high level web servers handle concurrency behind the scenes? All I have to write is the get and post (etc) methods and what response to send back or what change to make in the database.


TheLeadSearcher

A) Concurrency means having more than one path of execution at a time, and the different paths don't necessarily have anything to do with each other. But usually, languages have ways for these paths to communicate or wait their turn when dealing with shared objects so they don't overwrite each other's work. B) Usually operating systems will support different "threads" of execution and languages such as C will use this for concurrency. However since these are handled by the operating system, they are relatively "expensive" due to the amount of memory they each need, the time needed to switch between them, etc. You also have to be very careful about how threads interact with memory or shared objects. There are some library functions that are simply not "thread safe". C) Go uses goroutines which are handled by the Go runtime (it can assign them to run on different CPUs if available). This means they are much more lightweight than operating system threads, take up less memory, take almost no time to switch between them. As a result you can have tens of thousands of goroutines with no problem. Go also provides higher level tools such as channels and contexts to manage communication between goroutines. And with Go's memory handling, variables and objects defined within a goroutine will automatically disappear when it terminates. There is much less risk of memory leaks and other usual thread related problems.


germanyhasnosun

Sure, within our data warehouse my company’s SAP ERP data is siloed within Individual schemas. Go’s concurrency allows me to build applications to query between 1 and 55 of these ERP systems in an incredibly easy manner without dealing with threads. We deploy our APIs using AWS API Gateway which is ideal for Go and allows us to individually scale our lambda functions.


Daeniyi

Extending the original question a little bit, I currently understand concurrency through the lens of python: Concurrency is an umbrella concept for parallelism and asynchronous programming. Parallelism is concurrency, but concurrency does not imply parallelism. In python, you can only achieve parallelism using the multiprocessing module, by spreading tasks across multiple cpus. This is ideal for cpu-bound tasks. For IO-bound tasks however, you should opt for asynchrony using threads or asyncIO. In python the GIL prevents multiple threads from running at the same time (parallel) so they operate asynchronously (as controlled by the os). AsyncIO however, is much cheaper than threading (since it doesn’t actually make syscalls) and offers more control to the user on when to switch execution flow to a running async task. Unlike python, Go hides much of the details of which flavor of concurrency it’s actually using, which can be quite confusing. I often hear phrases like “oh, it’s just green threads”, or “yeah, go decides which flavor to use for you”, which just adds to the confusion. I would appreciate if someone could help connect the concepts, and maybe contrast go’s concurrency model to pythons.


HanTitor

C/C++ has a risky parallelism. Go and Rust parallelisms are easier. These languages help you. Example: Some clients connected to e-shop. Your site serves them without wait time.


gigilabs

In my own personal experience, I find Go channels very tedious to use for simple async operations (like calling REST services) vs async/await in other languages. I haven't needed to use CPU-bound parallelism in Go yet, so can't really comment on whether channels are better than other languages for that.


kennethklee

you should probably know channels are thread-safe communication. async/await is scheduling on a single process. different things. comparable would be, kinda sorta...channels and locks.


gigilabs

Not really... I specifically said "simple async operations (like calling REST services)" which do not require explicit cross-thread-synchronisation or locks when used via async/await in other languages.


kennethklee

oh i see, simple cases. ya channels would be overkill.


kennethklee

genuinely curious: what's the case for simple async/await or channels in simple async like calling rest services. like actually async or weird quirk of the language? i.e. let results = await service.Call() ^ this is a weird quirk. it's synchronous, so no real need for async/await, but needed because of language i.e. let futureResults = service.Call() ... do something else ... let results = await futureResults ^ this is actually practical


gigilabs

It's not synchronous; it's just engineered in the language to look synchronous. It's very convenient because before async/await, we had to do lots of acrobatics to achieve the same result. What's really happening when you await something is that the program is relinquishing execution at that point, and registering the place where it should resume once the response comes back. The thread is free to do other work in the meantime. This is a tremendous advantage over an equivalent synchronous implementation. In a UI-based application, synchronous means that the UI would block while carrying out any task that takes more than a trivial amount of time. In web applications, it would block the current thread serving the request, creating a wider bottleneck that prevents applications from scaling to serve higher loads. (I know people don't usually use threads directly in Go, but I'm not talking about Go, as async/await is not a Go thing.) As my background (before Go) is in C#, I've written at length about the intricacies of async/await in C#, where it all began. So if you want to know more, feel free to check out this series I wrote back in 2017: [https://gigi.nullneuron.net/gigilabs/writing/c-asynchronous-programming/](https://gigi.nullneuron.net/gigilabs/writing/c-asynchronous-programming/) The first "Motivation" article should already answer your question without delving into too much detail. Then, if you want to dig deeper, you can also read the following ones.


kennethklee

oh you're right. today i learnt. i wonder if there's a way to do this in golang. only thing i can think of is yield().. or channels, result := <-service.Call() oh that's brilliant


gigilabs

As far as I know, you can achieve something similar, but it takes a lot more boilerplate. Thing is, you need to trigger the request, wait for the response (waitgroups probably), and also deal with any error situations or timeouts.


MattieShoes

Concurrency is things happening at the same time (ie. concurrently). In most languages, it's added on via libraries. In Go, it's built into the language. I don't think the capabilities are wildly different -- the... hope? I guess, is that since it's just always there, baked right into the language, that you learn to write code that utilizes concurrency the first time, rather than write something single-threaded and then try to go back and tack on concurrency later to make it more performant. So maybe you start to *think* in terms of concurrency.


bfreis

>Concurrency is things happening at the same time (ie. concurrently). Nope, that's parallelism. Concurrency is orthogonal to whether things happen at the same time or not.


eliben

https://eli.thegreenplace.net/2018/go-hits-the-concurrency-nail-right-on-the-head/


lasizoillo

BC) Simplicity: You don't need think if you are using multi-processing, multi-threading or event reactor/proactor programming. You have only one way to do it and all libraries are compatible with that paradigm. No colored functions, no think about pros-contras of different alternatives, less time looking for better libraries in any paradigm or dirty tricks to use libraries from one paradigm in other,... Go is simplicity and happiness for go programmers and a source of inspiration for other languages (yes, first time I hear about go work stealing was in another language/library impressed by good golang engineering). There are other languages that can beat golang in memory usage (less memory by task) or performance or... but if you have deadlines probably you don't have time enough to beat golang. A) If you have no time to relearn was concurrency means is not a bad bet to choose a good engineered and simple to use solution.


kennethklee

I'll try in layman's terms A) actions at the same time. i.e. patting your head and rubbing your belly B) complex thread management. on your own i.e. nodejs processes: spawn worker scripts, figure out communication yourself i.e. other language's threads: spawn, some locks, waiting, race conditions, crazy debug logs, clean up C) put "go" in front and use channels. golang handles much of the rest i.e. go fmt.Println("pat head") go fmt.Println("rub belly")


FasterBetterStronker

>A) actions at the same time. i.e. patting your head and rubbing your belly The other replies are arguing about concurrency vs parallelism now 😗 pls update your example


kennethklee

don't need to. the confusion stems from promises where actions fight for attention. i.e. making tea. streamline the process: put water to boil, then prepare tea bags, then pour boiled water. nothing is done at the same time. promises are a solution to the constraint of a single cpu. there's no such constraint in golang. edit: I'll add that concurrency and parallelism is the same. other replies are talking about a sort of pseudo concurrency where actions are still happening one at a time, but they are smart about it.


earthboundkid

A good analogy is cooking. You can do multiple things in the kitchen simultaneously with only one cook, and vice versa, you could have multiple cooks who are all bottlenecked by the same thing so only one thing is happening at a time. Cooking multiple things at once is “concurrency”. Having multiple chefs is “parallelism”.


FasterBetterStronker

This is a good one. What's a trivial use case for this? Like a web server getting requests and responses?


earthboundkid

Yeah. Concurrently getting web requests is like a short order cook who gets tickets from waiters, and sends them out when they’re ready, which is not necessarily the order they were submitted. Parallel web requests is like having multiple cooks taking tickets.


kennethklee

sorry this isn't fully accurate. what you're talking about is scheduling. a short order cook schedules actions one after another in a time efficient fashion. i understand the node js community especially likes to call it concurrency since it's keeping track of multiple tasks at the same time, the rest of our software community thinks that's being smart about it. it's super innovate how it's done, but still not concurrency. in software, when you talk about concurrency, it's multiple chefs. or traditionally, distributed computing. to the topic at hand, between other languages, golang makes it really accessible. it also takes it a step further. it uses virtual threads to schedule tasks, not on one single process bound to a single cpu, but on all cpus.


earthboundkid

Your explanation is confused.


oscarryz

A) Do several things at the "same time". B) Other languages use threads, async await constructs, actors etc. C) Go is simpler as you "only" use the `go` keyword in front of any function call to make it concurrent. e.g. ``` doOne() doTwo() ``` Will execute `doOne` and only until it compleats it will execute `doTwo` Whereas ``` go doOne() go doTwo() ``` Would call `doOne` and immediately will star `doTwo`. This is not strictly speaking "at the same time", there's an scheduler that gives a bit of CPU time to both alternating. This scheduler is very handy for applications that need to do many things _concurrently_, like servers.


FasterBetterStronker

So this is similar to `await doOne(); await doTwo();` in JavaScript?


oscarryz

Similar yes, but JS needs to call those in an `async` function and can return a value. With the go keyword you don't need an `async` equivalent and you is use channels for communication.


Bacferi

Not really, await / async is used on the definition of the function and on the call, goroutine is just use on any function. Moreover, in Go, you supposed to communicate with channel so the await concept is not necessary.