It looks like you're new here. If you want to get involved, click one of these buttons!
Hi Matthias,
I know you are probably going to hate me for this ![]()
But do you have a somewhat comprehensive list of what is thread-safe in KLayout?
Out of interest (and because in some cases it would be somewhat useful) I made a kfactory version which in the new python 3.14 works with the freethreading (i.e. no GIL). I know that you mentioned before that KLayout should be reasonably thread-safe if you are not modifying a cell or any of it's children while working on it.
But to me it even fails on c.each_inst() when another cell is currently being built. each_inst() even fails for an empty cell. This fails with a topological sort error.
Do you have any advice how I could fix this or what might cause this?
If you would like to test it, the branch is here https://github.com/gdsfactory/kfactory/tree/add-3.14-threading
Test case is the following:
import time
from concurrent.futures import ThreadPoolExecutor
from threading import get_ident
import kfactory as kf
kf.config.logfilter.level = "DEBUG"
def busy_wait(duration: float) -> None:
"""Actively burn CPU time for `duration` seconds."""
start = time.perf_counter()
while (time.perf_counter() - start) < duration:
pass # no sleep, no yield — pure busy loop
@kf.cell
def straight(length: int) -> kf.KCell:
c = kf.KCell()
layer = c.kcl.layer(1, 0)
# c.shapes(layer).insert(kf.kdb.Box(length, 500))
# busy_wait(1)
# print(f"{length=}")
return c
def test() -> kf.kdb.Cell:
c = ly.create_cell(name=f"{get_ident()}")
c.name = str(c.cell_index())
return c
def main() -> None:
with ThreadPoolExecutor(max_workers=32) as executor:
start = time.perf_counter()
futures = [executor.submit(straight, i * 1000 + 100) for i in range(2000)]
for i, f in enumerate(futures):
try:
f.result()
except Exception as e:
raise RuntimeError(f"Failed to build for {i=!r}") from e
end = time.perf_counter()
print(f"Took {end - start:.2f} seconds")
if __name__ == "__main__":
main()
Comments
Hi @sebastian,
Well, I ran out of hate - there are a few people out there currently, who really deserve it, and I used up all of it, just for them.
KLayout is partially thread safe. Here are some rules:
In C++, as a rule of thumb, "const" methods are thread safe, "non-const" ones are not. As constness is documented in the API, this should give you some guideline. Note that when using "non-const" methods, other threads should not access the object, not even from "const" methods.
That architecture is governed by the requirement of having multiple drawing or analysis threads, like in the tiling processor. But a "divide and conquer" approach is possible.
KLayout does not call Python code from multiple threads in parallel as that was not possible before FT Python. I still can't imagine that Python itself is fully free threaded. Maybe they just replaced the GIL by more detailed locks, for example on the reference counters. A similar thing needs to happen in the C extensions and I don't think many of them are prepared already.
Matthias
Hi Matthias,
Thanks for the clarification.
I also assume that it's not easy to make other (non-destructive) things threadsafe? In particular I am thinking of
Cell.insert(const CellInstArray cell_inst_array)If that was threadsafe I could lock the other things behind locks and semaphores, but this one would probably take most of the times where it would stall other threads too much.
If it's not easily possible, I'll just put this adventure on ice for now
Thanks for the info anyways!
Sebastian
No, sorry. It's beyond the scope to make everything thread safe. Even if the core API was thread safe per function call, you still run in trouble at the above-API level without locks. If you mix write and read operations - there are always resources you share and where threads will interact.
For example, if two threads create cells and place instances inside the same top cell - what should the threads see when they query instances? A temporary snapshot of their own cells? The cells from the other thread, which are potentially semi-built? There are attributes that are affected by all writing threads, like a cell's bounding box.
Multi-threading is a super complex topic, and I honestly don't understand why people take it so lightly. There are a few strategies that are smooth to implement and safe to use, such as divide and conquer. A naive approach to multi-threading will end in applications that are unstable and a nightmare to debug. Imagine bugs like data corruption that happens once a week and - of course - only in your customer's environment and without being reproducible. That's the reality you face when you don't engineer algorithms for multi-thread safety, and I am speaking out of experience. A yes, you sacrifice some bandwidth due to thread serialization and you can have weird effects due to thread synchronization, but that is part of the engineering involved.
Matthias
Hi Matthias. I am fully aware. I don't want everything thread safe. I don't need everything thread safe to make it useful. I have a cache which makes sure that mutual execution on the same cell (and access to the same as well) is blocked until the cell is in locked state (essentially using a thread safe caching tool which makes sure that each hashed input has only one thread accessing/computing it).
I asked about the
insert, because if I make that one behind a thread lock as well, I might as well not do it at all, as that one is apart from the shapes operation the most used one when creating a (SiPho) mask.I am totally fine with you saying no. I thought it's worth asking (and if we are getting it, I'd play and evaluate usefulness). I will just link to here for future feature requests and say no.
And yes you are right, all the major projects need to declare whether it is safe to disable GIL for them (in the pyproject I believe). There is just a flag that you can run to override that safety mechanism in 3.14, which is what I was using for this test.
Sebastian