Skip to main content

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

Project description

Thread Mangement 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:

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 faciliated 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.

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 in the next 5 seconds, we will exit the verification loop.
            if not thread_utils.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 threadutils 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 thread_utils.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))

threadutils 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.0.tar.gz (8.1 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.0-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: threadgroup_manager-0.1.0.tar.gz
  • Upload date:
  • Size: 8.1 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.0.tar.gz
Algorithm Hash digest
SHA256 9bb461208e1a3613879bbdcbc35fe40c64b5d2732c1c2a8d33f326f3bb59afa4
MD5 84723fe3149dd186a4b7b48ca073bc9b
BLAKE2b-256 c44b734ed7efa50ac9e90fd677af5f1f4ab35f855333d4293e0d36ea8a368022

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for threadgroup_manager-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 455d034afbda6f7fce8569f41120afd91a65b0384261005a9793aaa83f1f1429
MD5 6c5c48281958ff007377c85a575387f8
BLAKE2b-256 8707bafff27833d4a2b50c1908759305e274b585e9d3d220dd235c2b60f6bf1c

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