Python thread safety (3.14 freethreading)

edited October 17 in Python scripting

Hi Matthias,

I know you are probably going to hate me for this :smiley:

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

  • edited October 19

    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:

    • Individual objects can coexist in separate threads. All shared resources (for example, the properties repository) should be thread safe. For example, you can create four threads, each having their own Layout object. So basically (if you have the memory), you can duplicate a layout, have four threads working on a separate copy and finally merge the results.
    • Most important objects such as Layout, Region etc. should be thread-safe when reading. So multiple threads can read from one Layout object for example. Also, multiple threads can read from the same Cell or Shapes container.
    • Write access is not thread-safe. Also reading from one thread and writing from another is not safe. Some write paths are enabled to be thread safe, like writing shapes into different cells from multiple threads. But those are exceptions build into the code for particular applications.

    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 :tongue:

    Thanks for the info anyways!
    Sebastian

Sign In or Register to comment.