Advanced Python (Fall 2017)/lecture6

From EIK wiki

Lecture 6: Concurrency

Concurrency is doing to things at once, not necessarily simultaneously.

Task 1.

Make a socket echo server

Solution

use google! this one is from https://pymotw.com/3/socket/tcp.html

# socket_echo_server.py

import socket
import sys

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 10000)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)

while True:
    # Wait for a connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print('connection from', client_address)

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(16)
            print('received {!r}'.format(data))
            if data:
                print('sending data back to the client')
                connection.sendall(data)
            else:
                print('no data from', client_address)
                break

    finally:
        # Clean up the connection
        connection.close()

You can connect with this echo server with the command nc localhost 10000

Need for concurrency

What happens if you try to open multiple connections to this socket server, and write a message?

Does it respond with an echo?

Async

python 3.4 adds a new library called asyncio which adds the ability to do ansychronous programming with python, using only the standard library. Asynchronous python, like javascript, has the ability to do something else when it is waiting.

function with async def are coroutines. actions which the async coroutine waits for are preceded with await and are generators.

Examples

Here is an example of doing things out of order, and sleeping without blocking: https://asyncio.readthedocs.io/en/latest/hello_clock.html

Here is a tcp echo server: https://asyncio.readthedocs.io/en/latest/tcp_echo.html

here is an example that will stay connected until the connection is closed by the client

import asyncio


async def handle_echo(reader, writer):
    while True:
        data = await reader.read(100)
        message = data.decode()
        addr = writer.get_extra_info('peername')
        print("Received %r from %r" % (message, addr))
        if len(message.strip()) == 0:
            print("Close the client socket")
            writer.close()
            break
        print("Send: %r" % message)
        writer.write(data)
        await writer.drain()


loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server = loop.run_until_complete(coro)

# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

What happens if you make multiple connections with this server?

Threads

Threads can run concurrently, and they share the same memory.

this will take about 1 second to run using threads. without threads it would take 100 seconds

import threading
import time
threads = []


def foo():
    time.sleep(1)


for i in range(100):
    t = threading.Thread(target=foo)
    threads.append(t)

for t in threads:
    t.start()

for t in threads:
    t.join()

Thread safety

If you run this code with python 2, you will get different results each time you run it. It is not thread safe.

import threading
threads = []


n = 0

def foo():
    global n
    n += 1

for i in range(1000):
    t = threading.Thread(target=foo)
    threads.append(t)

for t in threads:
    t.start()

for t in threads:
    t.join()

print(n)