What is the difference between "spawn_link" and "start_link"?

Hi everyone. I’m confused between “spawn_link” and “start_link”. Can you talk about their difference? Thanks :slight_smile:

spawn_link creates a linked process right where you are, start_link is a conventional name for a function that will eventually create a linked process whereas the process lifecycle is managed for you.


Hi Nobbz. Thank you for replying. Does process which is created by “start_link” be linked process right where you are such as “spawn_link”?

Yes it does too.

Hi @tqtrung, I’m not sue if I understand you correctly, but the convention is to link to the process which calls the start_link. This is not only due to its easy to understand, but also necessary that it works fine in a supervision tree.

Compared to spawn_link BIF, the main difference is that start_link is most often synchronous, which means that the function returns after the spawned process acknowledges that it has been initialized. In contrast, with spawn and spawn_link you don’t have any guarantees about synchronism. These functions might return before the new process has executed a single instruction.

To illustrate the difference, let’s look at the following snippet:

foo_pid = spawn_link(fn -> do_something() end)
bar_pid = spawn_link(fn -> do_something(foo_pid) end)

With this code, bar can’t assume anything about the progress of foo. It’s possible that e.g. 1000 instructions are executed in bar, while foo hasn’t executed the single instruction.

In contrast, if start_link is used (typically via GenServer or other higher-level wrappers), we’d have something like:

{:ok, foo_pid} = Foo.start_link()
{:ok, bar_pid} = Bar.start_link(foo_pid)

So here, the bar process is started only after foo has been initialized. This means that you can reliably reason about the order of execution. You can be certain that foo prepared the necessary initialization steps before moving forward with starting bar.

It’s also worth noting that start_link can return a synchronous error (the process decided to stop during its initialization). This is why start_link has the {:ok, pid} | {:error, start_error} format, while spawn always succeeds.

In addition, start_link usually involves a bit of extra bookkeeping. Typically, a start_link-ed process will keep track of its parent (starter process), and it will also store the initial MFA which was used to start the process. The former is used to implement a process hierarchy, and ensure proper process cleanup (to take down all descendants before terminating a process). The latter is mostly (if not completely) used for logging/debugging purposes.

It’s worth noting that spawn_link is a built-in function (aka BIF) implemented in C. In other words, it’s a basic feature of the runtime layer.

In contrast, various incarnations of start_link are implemented in Erlang, and you could make a custom version of such function yourself (which is IMO a nice exercise).

For more details, I suggest studying the code of the proc_lib module. Among other thing, this module contains the implementation of start_link which is used by most other start_link implementations, such as GenServer.