Is there a way to know from introspection metadata whether g_object_unref can be called from a thread that is not the main context’s thread? Or should we assume that no objects are thread-safe except those in GIO?
Good questions. Ideally, our documentation would be clear enough on these topics that you wouldn’t need to ask here, but I’m not surprised that’s not currently true.
Unless documented otherwise, you need to assume the GObject that you are working with is not threadsafe. In that case, you can’t share it on multiple threads at all. You should never wind up thinking about “can I call g_object_unref() on this thread or not?” because the object should never be used on any thread other than the thread that it was created on. i.e. no, it’s not safe, and you already lost before you got to this point.
Correct. Well, in theory, at least. I wouldn’t be surprised if there were threadsafety bugs in various functions.
Nah, GObjects count as data structures, I’m afraid, so they are generally not threadsafe. Only a few GObjects are documented to be threadsafe. You should start by assuming your object is not threadsafe, and modify that assumption only if the documentation explicitly tells you otherwise.
Sometimes the safety guarantees are complicated. For example, GIOStream is documented to be safe to use from one reader thread and one writer thread, but only if certain rules are followed. Other objects may be threadsafe, but only if you don’t call certain functions. For example, using g_object_add_weak_pointer() or g_signal_connect_object() on a threadsafe object is never safe if that object is used on multiple threads, because these functions are documented to not be threadsafe.
All that said, non-threadsafe objects should still generally be safe to create and use on secondary threads, as long as they stay on the thread that they are created on. Some libraries or applications might not follow this rule – e.g. GTK objects may only be safely used on the GTK thread, which is almost always the main thread – but anything provided by GLib itself will.
No, there’s no metadata for this. You just have to read the docs. And again, don’t assume GIO objects are threadsafe, because they’re usually not.
Everything in the GObject library is (supposed to be) thread-safe (with the notable exception of GBinding until the latest release), but in general like Michael said you’ll have to check the documentation. GStreamer would be an example of a library on top of GObject that is fully thread-safe.
The Rust bindings (as Rust has a way to mark and enforce thread-safety to some degree via its type system) are currently manually marking APIs as thread-safe or not and assume that everything not explicitly configured as thread-safe on the bindings-side is not.
Thanks for your quick reply and the link. That gave me quite a lot to think about.
The reason for asking relates to the SML bindings that I am developing. For both supported compilers, a hook into the garbage collector to run code for unreachable objects (currently just g_object_unref) occurs in its own thread. To some extent the bindings can dictate a policy. Ideally, this would just be simple guidance for the user to follow, e.g. if you have objects that must be freed from the thread in which their ownership was taken, not the thread of the default main context, you must create a local main context (but I haven’t done enough with multiple threads to know whether that is practical yet).
My initial thought of just using g_idle_add to schedule g_object_unref is potentially problematic for two reasons:
On termination, the main loop may have stopped iterating the default main context, so g_object_unref scheduled with g_idle_add wouldn’t get run. However, in that case it should be fine to run g_object_unref from the GC thread as there won’t be any race conditions (I think). The code for g_main_context_invoke_full looks like a useful guide for doing this. (I haven’t yet worked out whether that code guarantees that a source added is actually run - can the main loop exit after g_main_context_invoke_full tried to acquire the context but before the source was added?)
The default main context may not be suitable because the object was created in a thread that is not the one for the default main context and the object is not thread-safe. In this case, I think the user would need to create their own thread-local main context and the bindings would add g_object_unref as an idle source to that context. With that solution, the question of termination applies as for issue 1 , above, and I need to think about whether the bindings keep a strong or weak reference to a main context.
I’ll report back once I’ve done some experimentation.
The C# bindings are doing all unreffing from the default main context for that reason. That of course a) requires a main loop to actually run there and b) introduces a lot of overhead if lots of objects are from other threads. In case of GStreamer usage from C# this is a problem.
Not sure how other bindings are handling this but probably similar as they’ll likely be designed for GTK usage first and multi-threaded libraries were an afterthought.
For doing it from a local main context you would require the user to create one when creating objects, remember that one and on GC pass the unref to that?
GMainContext is unfortunately no GObject and doesn’t have any weak reference mechanisms