avatarTomas Svojanovsky

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4774

Abstract

w connection</span> connection, _ = server.accept() <span class="hljs-comment"># Create a new thread for each client connection</span> thread = Thread(target=receiver, args=(connection,)) <span class="hljs-comment"># Start the thread</span> thread.start()</pre></div><p id="948c">This solves the problem of multiple clients being unable to connect at the same time with blocking sockets, although the approach has some issues unique to threads. What happens if we try to kill this process with CTRL-C while we have clients connected?</p><figure id="af0a"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*ury40mKF5gmyAa94.gif"><figcaption></figcaption></figure><p id="bc9c">However, shutting down the application is not as straightforward as it may seem. If you attempt to terminate the application, you’ll likely encounter a <code>KeyboardInterrupt</code> exception thrown on the <code>server.accept()</code> call. Despite this exception, the application won't exit smoothly due to the presence of a background thread that keeps the program alive. As a result, any connected clients will still be able to send and receive messages, leading to potential issues.</p><p id="f559">One important thing to note is that user-created threads in Python do not receive <code>KeyboardInterrupt</code> exceptions; only the main thread is interrupted by such exceptions. Consequently, our threads will continue to run uninterrupted, reading data from clients and preventing the application from exiting gracefully.</p><figure id="fe85"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*7QuuK2nzQfX84NWo.gif"><figcaption></figcaption></figure><h2 id="b24d">Solution?</h2><p id="de50">When it comes to handling thread termination in Python, developers have a couple of approaches at their disposal. One common method is to utilize what are known as daemon threads, or alternatively, to implement a custom approach for canceling or interrupting running threads.</p><p id="4840">Daemon threads serve as a specialized type of thread designed for long-running background tasks. Unlike regular threads, daemon threads do not prevent the application from shutting down. In fact, when only daemon threads are active, the application will automatically terminate. It’s worth noting that Python’s main thread is not a daemon thread by default.</p><p id="d811">Creating daemon threads is straightforward. Developers simply need to set the <code>daemon</code> attribute to <code>True</code> before starting the thread using the <code>start()</code> method. However, one significant drawback of this approach is the lack of control over thread termination. Since daemon threads terminate abruptly when the main program exits, there's no opportunity to execute any cleanup or shutdown logic.</p><p id="c05d">While daemon threads offer simplicity and ease of implementation, they may not be suitable for scenarios where precise cleanup or resource management is required. In such cases, developers may opt to design their own thread termination mechanism, allowing for more fine-grained control over the shutdown process.</p><h2 id="b3d9">The second approach</h2><p id="be66">To do this, we’ll create threads slightly differently than before, by subclassing the Thread class itself. This will let us define our own thread with a cancel method, inside of which we can shut down the client socket. Then, our calls to recv and sendall will be interrupted, allowing us to exit our while loop and close out the thread.</p><div id="b975"><pre><span class="hljs-keyword">from</span> threading <span class="hljs-keyword">import</span> Thread <span class="hljs-keyword">import</span> socket

<span class="hljs-keyword">class</span> <span class="hljs-title class_">ClientEchoThread</span>(<span class="hljs-title class_ inherited__">Thread</span>): <span class="hljs-keyword">def</span> <span class="hljs-title function_">init</span>(<span class="hljs-params">self, client</span>): <span class="hljs-built_in">super</span>().init() self.client = client

<span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self</span>):
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># Continuously receive data from the client</span>
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = self.client.recv(<span class="hljs-number">2048</span>)
            <span class="hljs-comment"># If no data is received, the connection is closed</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> data:
                <span

Options

class="hljs-keyword">raise</span> BrokenPipeError(<span class="hljs-string">"Connection closed!"</span>) <span class="hljs-comment"># Print the received data</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Data received <span class="hljs-subst">{data}</span>!"</span>) <span class="hljs-comment"># Send the received data back to the client</span> self.client.sendall(data)

    <span class="hljs-keyword">except</span> OSError <span class="hljs-keyword">as</span> e:
        <span class="hljs-comment"># Handle any OSError (e.g., connection reset by peer)</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Thread interrupted by <span class="hljs-subst">{e}</span> exception, shutting down!"</span>)

<span class="hljs-keyword">def</span> <span class="hljs-title function_">close</span>(<span class="hljs-params">self</span>):
    <span class="hljs-comment"># Check if the thread is still running</span>
    <span class="hljs-keyword">if</span> self.is_alive():
        <span class="hljs-comment"># Send a message to the client indicating the shutdown</span>
        self.client.sendall(<span class="hljs-built_in">bytes</span>(<span class="hljs-string">"Shutting down!"</span>, encoding=<span class="hljs-string">"utf-8"</span>))
        <span class="hljs-comment"># Shutdown the client connection for both reads and writes</span>
        self.client.shutdown(socket.SHUT_RDWR)

<span class="hljs-comment"># Create a TCP/IP socket</span> <span class="hljs-keyword">with</span> socket.socket(socket.AF_INET, socket.SOCK_STREAM) <span class="hljs-keyword">as</span> server: <span class="hljs-comment"># Allow the socket to be reused immediately after it is closed</span> server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="hljs-number">1</span>) <span class="hljs-comment"># Bind the socket to the address and port</span> server.bind((<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">8000</span>)) <span class="hljs-comment"># Listen for incoming connections</span> server.listen() <span class="hljs-comment"># List to store connection threads</span> connection_threads = []

<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        <span class="hljs-comment"># Accept a new connection</span>
        connection, addr = server.accept()
        <span class="hljs-comment"># Create a new thread for each client connection</span>
        thread = ClientEchoThread(connection)
        <span class="hljs-comment"># Add the thread to the list</span>
        connection_threads.append(thread)
        <span class="hljs-comment"># Start the thread</span>
        thread.start()
<span class="hljs-keyword">except</span> KeyboardInterrupt:
    <span class="hljs-comment"># Handle KeyboardInterrupt (Ctrl+C)</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Shutting down!"</span>)
    <span class="hljs-comment"># Close all connection threa</span></pre></div><p id="c706">Overall, canceling running threads in Python, and in general, is a tricky problem and depends on the specific shutdown case you’re trying to handle. You’ll need to take special care that your threads do not block your application from exiting and to figure out where to put in appropriate interrupt points to exit your threads.</p><p id="7766">If you enjoyed the read and want to be part of our growing community, hit the follow button, and let’s embark on a knowledge journey together.</p><p id="7959">Your feedback and comments are always welcome, so don’t hold back!</p><h1 id="cfc0">In Plain English 🚀</h1><p id="6207"><i>Thank you for being a part of the <a href="https://plainenglish.io"><b>In Plain English</b></a> community! Before you go:</i></p><ul><li>Be sure to <b>clap</b> and <b>follow</b> the writer ️👏<b>️️</b></li><li>Follow us: <a href="https://twitter.com/inPlainEngHQ"><b>X</b></a><b> | <a href="https://www.linkedin.com/company/inplainenglish/">LinkedIn</a> | <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw">YouTube</a> | <a href="https://discord.gg/in-plain-english-709094664682340443">Discord</a> | <a href="https://newsletter.plainenglish.io/">Newsletter</a></b></li><li>Visit our other platforms: <a href="https://stackademic.com/"><b>Stackademic</b></a><b> | <a href="https://cofeed.app/">CoFeed</a> | <a href="https://venturemagazine.net/">Venture</a></b></li><li>More content at <a href="https://plainenglish.io"><b>PlainEnglish.io</b></a></li></ul></article></body>

Speed up Your Blocking I/O Code with Multithreading

A large portion of our work may be managing existing code using blocking I/O libraries, such as requests for HTTP requests, psycopg for Postgres databases, or any number of blocking libraries.

The intriguing aspect of blocking I/O operations in Python is that they release the GIL during their execution. This behavior enables the possibility of running I/O operations concurrently in separate threads, effectively harnessing the power of parallelism within a Python application. By leveraging threading, developers can design applications that efficiently handle multiple I/O operations simultaneously, enhancing performance and responsiveness.

You say we can do multithreading?

The Python interpreter operates in a single-threaded manner within a process, which means that only one piece of Python bytecode can be executed at any given time, even if multiple threads are active. This constraint is enforced by the Global Interpreter Lock (GIL), which permits only one thread to execute Python bytecode at a time.

While this arrangement may appear restrictive for maximizing the benefits of multithreading, there are specific scenarios in which the GIL is temporarily released. One such scenario occurs during I/O (input/output) operations. Python relinquishes the GIL during I/O operations because, at a low level, the language relies on operating system calls to carry out these tasks. These system calls operate independently of the Python interpreter, meaning that Python bytecode does not need to be executed while waiting for I/O operations to finish. Consequently, during these periods, other threads can continue executing Python bytecode concurrently, facilitating simultaneous I/O operations despite the presence of the GIL.

Sockets…

Recently I was talking about sockets. Imagine you have a project where non blocking sockets are not option. What now?

If you find yourself in a project where non-blocking sockets are not feasible, there’s still a viable solution. Since the recv and sendall methods of sockets are I/O-bound and therefore release the Global Interpreter Lock (GIL), it's possible to execute them concurrently in separate threads.

This approach involves creating one thread per connected client, where each thread is responsible for handling the read and write operations of its associated client. This model, known as the thread-per-connection model, is commonly used in web servers like Apache.

This model is a common paradigm in web servers such as Apache and is known as a thread-per-connection model.

from threading import Thread
import socket

# Function to handle receiving and echoing data from a client
def receiver(client_socket: socket):
    while True:
        # Receive data from the client
        data = client_socket.recv(2048)
        # Print the received data
        print(f"Data received: {data}")
        # Echo the received data back to the client
        client_socket.sendall(data)

# Create a TCP/IP socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    # Allow the socket to be reused immediately after it is closed
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # Bind the socket to the address and port
    server.bind(("127.0.0.1", 8000))
    # Listen for incoming connections
    server.listen()

    while True:
        # Accept a new connection
        connection, _ = server.accept()
        # Create a new thread for each client connection
        thread = Thread(target=receiver, args=(connection,))
        # Start the thread
        thread.start()

This solves the problem of multiple clients being unable to connect at the same time with blocking sockets, although the approach has some issues unique to threads. What happens if we try to kill this process with CTRL-C while we have clients connected?

However, shutting down the application is not as straightforward as it may seem. If you attempt to terminate the application, you’ll likely encounter a KeyboardInterrupt exception thrown on the server.accept() call. Despite this exception, the application won't exit smoothly due to the presence of a background thread that keeps the program alive. As a result, any connected clients will still be able to send and receive messages, leading to potential issues.

One important thing to note is that user-created threads in Python do not receive KeyboardInterrupt exceptions; only the main thread is interrupted by such exceptions. Consequently, our threads will continue to run uninterrupted, reading data from clients and preventing the application from exiting gracefully.

Solution?

When it comes to handling thread termination in Python, developers have a couple of approaches at their disposal. One common method is to utilize what are known as daemon threads, or alternatively, to implement a custom approach for canceling or interrupting running threads.

Daemon threads serve as a specialized type of thread designed for long-running background tasks. Unlike regular threads, daemon threads do not prevent the application from shutting down. In fact, when only daemon threads are active, the application will automatically terminate. It’s worth noting that Python’s main thread is not a daemon thread by default.

Creating daemon threads is straightforward. Developers simply need to set the daemon attribute to True before starting the thread using the start() method. However, one significant drawback of this approach is the lack of control over thread termination. Since daemon threads terminate abruptly when the main program exits, there's no opportunity to execute any cleanup or shutdown logic.

While daemon threads offer simplicity and ease of implementation, they may not be suitable for scenarios where precise cleanup or resource management is required. In such cases, developers may opt to design their own thread termination mechanism, allowing for more fine-grained control over the shutdown process.

The second approach

To do this, we’ll create threads slightly differently than before, by subclassing the Thread class itself. This will let us define our own thread with a cancel method, inside of which we can shut down the client socket. Then, our calls to recv and sendall will be interrupted, allowing us to exit our while loop and close out the thread.

from threading import Thread
import socket

class ClientEchoThread(Thread):
    def __init__(self, client):
        super().__init__()
        self.client = client

    def run(self):
        try:
            # Continuously receive data from the client
            while True:
                data = self.client.recv(2048)
                # If no data is received, the connection is closed
                if not data:
                    raise BrokenPipeError("Connection closed!")
                # Print the received data
                print(f"Data received {data}!")
                # Send the received data back to the client
                self.client.sendall(data)

        except OSError as e:
            # Handle any OSError (e.g., connection reset by peer)
            print(f"Thread interrupted by {e} exception, shutting down!")

    def close(self):
        # Check if the thread is still running
        if self.is_alive():
            # Send a message to the client indicating the shutdown
            self.client.sendall(bytes("Shutting down!", encoding="utf-8"))
            # Shutdown the client connection for both reads and writes
            self.client.shutdown(socket.SHUT_RDWR)

# Create a TCP/IP socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    # Allow the socket to be reused immediately after it is closed
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # Bind the socket to the address and port
    server.bind(("127.0.0.1", 8000))
    # Listen for incoming connections
    server.listen()
    # List to store connection threads
    connection_threads = []

    try:
        while True:
            # Accept a new connection
            connection, addr = server.accept()
            # Create a new thread for each client connection
            thread = ClientEchoThread(connection)
            # Add the thread to the list
            connection_threads.append(thread)
            # Start the thread
            thread.start()
    except KeyboardInterrupt:
        # Handle KeyboardInterrupt (Ctrl+C)
        print("Shutting down!")
        # Close all connection threa

Overall, canceling running threads in Python, and in general, is a tricky problem and depends on the specific shutdown case you’re trying to handle. You’ll need to take special care that your threads do not block your application from exiting and to figure out where to put in appropriate interrupt points to exit your threads.

If you enjoyed the read and want to be part of our growing community, hit the follow button, and let’s embark on a knowledge journey together.

Your feedback and comments are always welcome, so don’t hold back!

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

Python
Multithreading
Programming
Software Development
Software Engineering
Recommended from ReadMedium