# Does stdlib network sockets use only first thread?

**URL:** <https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027>\
**Category:** General Usage\
**Tags:** networking, multithreading\
**Created:** [November 24, 2024, 9:33pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027 "2024-11-24T21:33:18Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Pedro](https://avatars.discourse-cdn.com/v4/letter/p/919ad9/32.png) [@Pedro](https://discourse.julialang.org/u/Pedro)\
**Post date:** [November 24, 2024, 9:33pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/1 "2024-11-24T21:33:18Z")

</div>

I am writing a multi-threaded applications with a GUI (CImGui.jl) and a network sockets (Base.Sockets) to handle low-latency data stream over local network via UDP.

I have a few independent threads to handle data processing and networking. First `UDPSocket` (source of data) and `recv` calls in one thread, and second `UDPSocket` (sink for processed data) and `send` calls in another thread. Data exchange between threads is done via `Channel`.

After various test why the output network stream lags, I found two cases:

1. When I spawn and pin function that handles GUI to thread with `Threads.threadid() == 1`, the frequency of `send` call decreases noticeably, and the `Channel` that is the source of data for `send` calls saturates.
2. When I spawn and pin function that handles GUI to thread with `Threads.threadid() == 2`, the frequency of `send` calls is equal to the frequency of incoming data, and the `Channel` that is the source of data for `send` calls is almost always empty whenever I am checking.

So do functions from `Base.Sockets` somehow use only thread `Threads.threadid() == 1`? Is such behavior of program is expected? At this moment, I didn’t found any information related to this in documentation. I would appreciate if somebody could clarify or point the relevant documentation/source code.

I can try to reproduce it with MWE, if needed. 🙂

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [November 24, 2024, 10:05pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/2 "2024-11-24T22:05:16Z")

</div>

I don‘t know how your pinning works but it might lead to unexpected behavior as Julia is free to migrate tasks between threads.

---

<div class="post-metadata">

**Author:** ![Pedro](https://avatars.discourse-cdn.com/v4/letter/p/919ad9/32.png) [@Pedro](https://discourse.julialang.org/u/Pedro)\
**Post date:** [November 24, 2024, 10:11pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/3 "2024-11-24T22:11:12Z")

</div>

As far as I know CImGui.jl pins the task with `renderloop` to specific thread in the following way:

```julia
# Helper function to pin tasks to a specific thread. We need this because
# OpenGL/GLFW/ImGui are not threadsafe so task migration could cause segfaults.
function pintask!(task::Task, tid::Integer)
    if tid ∉ Threads.threadpooltids(:default) && tid ∉ Threads.threadpooltids(:interactive)
        error("Thread ID '$tid' does not exist in the :default or :interactive threadpool, cannot schedule a task onto it.")
    end

    task.sticky = true
    ret = ccall(:jl_set_task_tid, Cint, (Any, Cint), task, tid - 1)

    if Threads.threadid(task) != tid
        error("jl_set_task_tid() onto Julia thread ID $tid failed!")
    end
end

```

Source: [Gnimuc/CImGui.jl](https://github.com/Gnimuc/CImGui.jl/blob/master/src/CImGui.jl#L77)

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [November 24, 2024, 10:46pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/4 "2024-11-24T22:46:48Z")

</div>

I think that should be fine. I don‘t have any idea on the socket question though. Do you have a reproducible example you can share?

---

<div class="post-metadata">

**Author:** ![Pedro](https://avatars.discourse-cdn.com/v4/letter/p/919ad9/32.png) [@Pedro](https://discourse.julialang.org/u/Pedro)\
**Post date:** [November 24, 2024, 11:57pm UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/5 "2024-11-24T23:57:51Z")

</div>

I have created a minimal work example that yields the same behavior. Keyword argument `spawn` for function `app` sets `threadid` for a `renderloop`. Check comments at the end of the source code. Let me know what timings you get.

I am using Julia v1.11.1 on x86\_64 Linux (Glibc).

Packages to reproduce:

```julia
] add CImGui ModernGL GLFW Sockets

```

I am using following command to run Julia:

```sh
julia --project --threads auto

```

```julia
using Sockets
import GLFW
import CImGui
import ModernGL
CImGui.set_backend(:GlfwOpenGL3)

function producer(run::Ref{Bool})
    sockd = UDPSocket()
    i = 0
    last = time()
    try
        while run[]
            x = rand(Int16, 480)
            send(sockd, ip"127.0.0.1", 8080, x)
            sleep(0.001)
            if i % 100 == 0
                t = time()
                @info "Datagram#$(i); Time to send 100 datagrams: $(t - last)"
                last = time()
            end
            i += 1
        end
    finally
        sockd |> close
        @warn "producer exited"
    end
end

function app(; engine=nothing, spawn=1)
    ctx = CImGui.CreateContext()

    run = Ref(true)
    Threads.@spawn producer(run)

    CImGui.render(ctx; engine, window_title="MWE", spawn=spawn) do
        CImGui.Begin("debug")
        CImGui.Text("app thread id $(Threads.threadid())")
        CImGui.End()
        # CImGui.ShowMetricsWindow()
        # CImGui.ShowDemoWindow()
    end

    run[] = false
end

app(spawn=1);
# If spawn set to 1, Time to send 100 datagrams by producer is about 3.3 seconds
# If spawn set to 2, Time to send 100 datagrams by producer is about 0.25 seconds

```

---

<div class="post-metadata">

**Author:** ![JamesNZ](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jamesnz/32/35361_2.png) [@JamesNZ](https://discourse.julialang.org/u/JamesNZ)\
**Post date:** [December 4, 2024, 10:06am UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/6 "2024-12-04T10:06:12Z")

</div>

Heyo, I’m one of the CImGui maintainers 🙂 What you’re seeing is expected because by default the libuv event loop only runs on thread 1. This is documented in the `render()` docs: [Backends · CImGui.jl](https://gnimuc.github.io/CImGui.jl/stable/backends/#CImGui.render)

In particular:

> `spawn::Union{Bool, Integer, Symbol}=1`: How/where to spawn the renderloop. It defaults to thread 1 for safety, but note that currently Julia also uses thread 1 to run the libuv event loop: [#50643](https://github.com/JuliaLang/julia/issues/50643). The renderloop does `yield()` on each iteration but it’s still likely to hog thread 1 which may cause libuv things like task switching to become slower. In most cases this is unlikely to be a problem, but keep it in mind if you observe IO/task heavy things being slower than you’d expect.

The main reason for pinning is to prevent task migration from one thread to another, that would break all sorts of things. The reason for pinning to thread 1 is only because that’s what GLFW recommends, but (AFAIU) that’s mostly because of OSX: [Multithreading GLFW? - #5 by elmindreda - support - GLFW](https://discourse.glfw.org/t/multithreading-glfw/573/5)

TL;DR: if you’re running on Linux you can choose any thread you like (I would suggest one in the interactive threadpool). A different thread _might_ work on Windows and will likely not on OSX (but I’ve never tried it so who knows).

---

<div class="post-metadata">

**Author:** ![Pedro](https://avatars.discourse-cdn.com/v4/letter/p/919ad9/32.png) [@Pedro](https://discourse.julialang.org/u/Pedro)\
**Post date:** [December 4, 2024, 10:16am UTC](https://discourse.julialang.org/t/does-stdlib-network-sockets-use-only-first-thread/123027/7 "2024-12-04T10:16:17Z")

</div>

Thanks for detailed reply and pointing the right place in the documentation. 🙂  
I don’t know why I didn’t notice that. ☹

I also learnt just yesterday that event loop runs only on thread 1 in another topic [Non-blocking network IO - #9 by Pedro](https://discourse.julialang.org/t/non-blocking-network-io/123344/9) via great JuliaCon talk [A retrospect on Julia web services: HTTP, performance and memory | Guliński, Georgakopoulos](https://www.youtube.com/watch?v=veZ7xB62olI).
