Skip to main content

A Python library for hierarchical thread group management with event-based coordination.

Project description

Thread Management Library

This library addresses the below requirements:

  • Logically group Thread objects, with thread pooling functionality, to limit the number of concurrent threads.
  • Maintaining a parent-child hierarchy of threads, addressing the limitation of Python's built-in library wherein all threads are equal.
  • Allowing threads to communicate with their siblings or children through an abstracted event based system.

Threads are grouped by ThreadGroup objects. A ThreadGroup object is created in the parent thread, and tasks are added as per the use case. We can specify the maximum number of concurrent threads this group should hold, at instantiation time.

Example usage:

from threadmanager import ThreadGroup

thread_group = ThreadGroup("Verifying Users", max_concurrent_threads=10)  
# Thread group to verify users in a data structure parallely

for user in users:
    thread_group.add_task(verify_user, args=(user))
    # If number of users are greater than 10, 
    # ThreadGroup will automatically limit the number of active threads

thread_group.join(600)  #  Waits for threads to terminate with a timeout of 600 seconds.

What if we wanted just one verified user? There is no default way for a thread to be aware that this condition is met. Neither is there a mechanism to kill an active thread, as that would be unsafe.

Every time a ThreadGroup object is created there is an associated Event object that is maintained. The parent thread has this context through the ThreadGroup object, and all the threads spawned by the thread group (i.e. the child threads) are aware of this object too. This is facilitated by maintaining an n-ary tree to track the hierarchy.

If two thread group objects are created inside the main thread, the tree's state will be

         MainThread(ThreadEventTreeNode)
                    /    \
                   /      \
                  /        \
                 /          \
      ThreadGroup1         ThreadGroup2

Each node has an Event object associated with it. And each thread group would have spawned several worker threads which are logically encapsulated inside the ThreadGroup object.

         MainThread(ThreadEventTreeNode)
                    /    \
                   /      \
                  /        \
                 /          \
      ThreadGroup1         ThreadGroup2
       ~ Thread1            ~ Thread4
       ~ Thread2            ~ Thread5
       ~ Thread3            ~ Thread6

Each thread group has 3 threads. There are 2 use cases for thread communication:

  • A parent thread signalling to its children.
  • A child thread signalling to its siblings.

Let's consider the first case and the earlier example of User verification.

from threadmanager import event_aware_sleep

valid_users = []

def verify_user(user):
    retries = 3
    while retries > 0:
        try:
            verify_user_internal()  # internal call made to verify user
            valid_users.append(user)
        except UserVerificationException:
            logger.info("Failed to verify user, sleeping for 5 seconds and retrying")

            # At this stage, if the event is set before the next 5 seconds, we will exit the verification loop.
            if not event_aware_sleep(5):  
                break

Note that checks for the state of the event can be added in key places as required, to ensure that we are not unnecessarily performing operations, and we are safely terminating threads.

When event-aware methods are used by child threads, it automatically fetches the event associated with the thread group of the currently executing child thread. If no event is found, i.e. this is the main thread with no parent group, then event_aware_sleep will perform a regular sleep.

thread_group = ThreadGroup("Verifying Users")
for user in users:
    thread_group.add_task(verify_user, args=(user))

threadmanager cannot auto-fetch the event associated with the current (parent) thread, as it could have created several different ThreadGroups. It needs context of which ThreadGroup to perform operations on, so its usage would be through the ThreadGroup object.

thread_group.wait_for_condition(lambda: len(valid_users) > 0, timeout=600)

The parent thread will wait for the length of the list valid_users to be greater than zero, and then inform the child threads to terminate, since we need just one valid user.

thread_group.join(600)

The ThreadEvents Controller maintains the hierarchy of all the thread groups created.

def worker_method():
    # We are creating multiple thread groups inside this method, each executing respective tasks in parallel
    thread_group_2_1 = ThreadGroup()
    thread_group_2_2 = ThreadGroup()
    thread_group_2_3 = ThreadGroup()

thread_group_2 = ThreadGroup()
thread_group_2.add_task(worker_method)
         MainThread(ThreadEventTreeNode)
                    /    \
                   /      \
                  /        \
                 /          \
      ThreadGroup1         ThreadGroup2
                            /    |    \
                           /     |     \
                          /      |      \
                         /       |       \
           ThreadGroup2.1        |        ThreadGroup2.3
                                 |
                           ThreadGroup2.2

Let's say ThreadGroup2 realizes it needs to abort all operations, We can simply call

thread_group_2.set_event_for_child_tasks()

and all events for the thread group and its child thread groups will be set, effectively signalling all threads spawned as part of this set of operations.

The hierarchy is maintained and computed, completely abstracted. It doesn't require any additional consideration when creating a ThreadGroup object.

It is also possible for the n-ary tree to become a forest, in case we spawn daemon threads from a thread group, and they still need to communicate amongst each other. Information about this disjoint tree will also be maintained automatically.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

threadgroup_manager-0.1.2.tar.gz (8.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

threadgroup_manager-0.1.2-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file threadgroup_manager-0.1.2.tar.gz.

File metadata

  • Download URL: threadgroup_manager-0.1.2.tar.gz
  • Upload date:
  • Size: 8.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for threadgroup_manager-0.1.2.tar.gz
Algorithm Hash digest
SHA256 6e43db4ca5c3932718c78d1c76bb5e105ffde66ddc4a0ed17c396207fba539fa
MD5 7eddc14dfacb4abd36e32a6f53b1f996
BLAKE2b-256 e358ef21e9b547cbc3d1ba306d5f3accfb013193b53fc73a223014ba1a5e4441

See more details on using hashes here.

File details

Details for the file threadgroup_manager-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for threadgroup_manager-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 cb98a51e6d0cbcc7a5eea19c80fdb1cb9e038bc665f492d8268d67cffa3541b3
MD5 e3a1dce7bea826e36a60fd124bdd2d24
BLAKE2b-256 880c77791c1880a1492db8734cbe6e0fef0e494f3b2130e99b45d6e45e5d80b4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page