# Trouble running multiple infinite loops (GTK + X11 + Signal listener) in parallel in Crystal

**URL:** <https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466>\
**Category:** Help & Support\
**Created:** [October 10, 2025, 6:06am UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466 "2025-10-10T06:06:06Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![VC365](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/vc365/32/3050_2.png) [@VC365](https://forum.crystal-lang.org/u/VC365)\
**Post date:** [October 10, 2025, 6:06am UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466/1 "2025-10-10T06:06:06Z")

</div>

I have an applet originally written in C, and after learning Crystal I decided to rewrite it.  
Everything went fine until I reached the part that needs multiple infinite loops running in parallel.

In the original C version I had three concurrent loops:

1. The GTK main loop
2. An X11 key listener
3. A signal listener (`SIGUSR1`)

When I tried to do the same in Crystal, the program either froze, lagged, or crashed depending on which threading method I used.

Here is the X11 loop:

```crystal
def keys
  Xlib.call do
    xInitThreads()
    dpy = xOpenDisplay(nil)
    unless dpy
      STDERR.puts("Cannot open display")
      return
    end
    root = xRootWindow(dpy, 0)

    xGrabKey(dpy, xKeysymToKeycode(dpy, X11::XF86XK_AudioLowerVolume),
             X11::AnyModifier, root, 1, X11::GrabModeAsync, X11::GrabModeAsync)
    xGrabKey(dpy, xKeysymToKeycode(dpy, X11::XF86XK_AudioRaiseVolume),
             X11::AnyModifier, root, 1, X11::GrabModeAsync, X11::GrabModeAsync)
    xGrabKey(dpy, xKeysymToKeycode(dpy, X11::XF86XK_AudioMute),
             X11::AnyModifier, root, 1, X11::GrabModeAsync, X11::GrabModeAsync)

    xSelectInput(dpy, root, X11::KeyPressMask)
    ev = uninitialized LRoot::XEvent

    loop do
      xNextEvent(dpy, pointerof(ev))
      if ev.type == X11::KeyPress
        keysym = xKeycodeToKeysym(dpy, ev.key.keycode, 0)
        case keysym
        when X11::XF86XK_AudioRaiseVolume
          Volume.volume_up
          Volume.update_safe(true)
        when X11::XF86XK_AudioLowerVolume
          Volume.volume_down
          Volume.update_safe(true)
        when X11::XF86XK_AudioMute
          Volume.volume_mute
          Volume.update_safe(false)
        end
      end
    end
  end
end

```

Signal handling code:

```auto
private def pid; "/tmp/volume-pulse.pid"; end

def signal_root
  File.write(pid, "#{Process.pid}")
  Signal::USR1.trap { Volume.update_safe(true) }
  sleep
end

def signal_update
  return unless File.exists?(pid)
  Process.signal(Signal::USR1, File.read(pid).to_i)
end

```

GTK loop:

```crystal
Xlib.call do
  notify("Volume Pulse") if ConfigVariables.use_notifications
  fcInit()
  gtk2_init(nil, nil)

  icon_tray = gtk2_status_new()
  Volume.icon_statusX do |volume|
    Xlib.call do
      if Volume.get_mute
        gtk2_status_icon_tooltip_text(icon_tray, "Volume: Muted")
        gtk2_status_set_icon_name(icon_tray, "audio-volume-muted")
      else
        gtk2_status_icon_tooltip_text(icon_tray, "Volume: #{volume}")
        case volume
        when 0
          gtk2_status_set_icon_name(icon_tray, "audio-volume-muted")
        when 1..30
          gtk2_status_set_icon_name(icon_tray, "audio-volume-low")
        when 31..70
          gtk2_status_set_icon_name(icon_tray, "audio-volume-medium")
        else
          gtk2_status_set_icon_name(icon_tray, "audio-volume-high")
        end
      end
    end
  end

  Volume.update

  g_signal_connect(icon_tray.as(LRoot::GObject*), "scroll-event", Event.scroll.as(LRoot::GCallback*), nil)
  g_signal_connect(icon_tray.as(LRoot::GObject*), "button-press-event", Event.button.as(LRoot::GCallback*), nil)
  g_signal_connect(icon_tray.as(LRoot::GObject*), "popup-menu", GUI.menu.as(LRoot::GCallback*), nil)

  gtk2_main()
  notify_uninit() if ConfigVariables.use_notifications
end

```

Methods I have tried:

- `g_thread_new` and `pthread_create` — both cause freezing or crashes
- `Thread.new` and `spawn` (with `-Dpreview-mt`) — sometimes only part of the signals are received, or it crashes after a short time, or one of the loops stops running completely

If I remove the GTK loop, everything works fine.  
But as soon as I call `gtk2_main`, all other threads or fibers stop responding.

Is there a reliable way to run multiple native event loops (GTK, X11, signal listener) in true parallel execution in Crystal?  
Or should I move one of these loops (for example the X11 listener) into a small C helper process instead?

**Crystal version:** Crystal 1.17.1 (2025-07-22)  
**OS:** Artix Linux (Openrc)  
**LLVM:** 20.1.8  
**Default target:** x86\_64-pc-linux-gnu

---

<div class="post-metadata">

**Author:** ![GeopJr](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/geopjr/32/1051_2.png) [@GeopJr](https://forum.crystal-lang.org/u/GeopJr)\
**Post date:** [October 10, 2025, 11:29am UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466/2 "2025-10-10T11:29:15Z")

</div>

> [@VC365](#):
>
> Is there a reliable way to run multiple native event loops (GTK, X11, signal listener) in true parallel execution in Crystal?

Give [`Fiber::ExecutionContext`](https://crystal-lang.org/api/1.17.1/Fiber/ExecutionContext.html) a try

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [October 10, 2025, 11:39am UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466/3 "2025-10-10T11:39:59Z")

</div>

Execution contexts could help. Each blocking loop should run in a separate instance of [`ExecutionContext::Isolated`](https://crystal-lang.org/api/1.17.1/Fiber/ExecutionContext/Isolated.html).

However, there’s also a good chance that some of the code you’re using is just not multi-threading safe. That applies to parts of stdlib, as well as many 3rd party shards.  
See [Charting the route to multi-threading support](https://forum.crystal-lang.org/t/charting-the-route-to-multi-threading-support/7320) for some details on how potential thread-safety issues.

You could try to reduce the code that runs in the blocking loops to a minimum. They can just trigger a notification - for example by sending to a channel - while the actual handler runs in the single-threaded main context - at the receiving end of the channel. That should avoid many thread-safety concerns because most of the code runs in a single thread.

---

<div class="post-metadata">

**Author:** ![VC365](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/vc365/32/3050_2.png) [@VC365](https://forum.crystal-lang.org/u/VC365)\
**Post date:** [October 10, 2025, 2:21pm UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466/4 "2025-10-10T14:21:34Z")

</div>

Thank you so much,Your guidance helped me a lot and I could solve the issues

---

<div class="post-metadata">

**Author:** ![VC365](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/vc365/32/3050_2.png) [@VC365](https://forum.crystal-lang.org/u/VC365)\
**Post date:** [October 11, 2025, 12:13pm UTC](https://forum.crystal-lang.org/t/trouble-running-multiple-infinite-loops-gtk-x11-signal-listener-in-parallel-in-crystal/8466/5 "2025-10-11T12:13:32Z")

</div>

You better update this section.

> **[Parallelism & Concurrency | Ultimate GTK4 Crystal Guide](https://ultimate-gtk4-crystal-guide.geopjr.dev/en/extra/parallelism-and-concurrency.html)**
>
> Learn how to create premium GTK4 apps in Crystal
