---
# System prepended metadata

title: CSIE 4004 HW4 report

---

# CSIE 4004 HW4 report

### Problem description
design a 3 threads python program, first thread is responsible for accepting clients' requests and return a number `NUM` based on number `A` and number `B`, and second thread is responsible for updating number `A` by calling a API regularly. third thread is responsible for updating number `B` by calling another API regularly. you can init the update an arbitrary interval t for second and third thread.

### first prompt
> Enter the description above.
```python=
import threading
import time
import random

# Global variables
NUM = 0
A = 0
B = 0

# Function to handle client requests
def handle_requests():
    global NUM, A, B
    while True:
        # Simulating client request processing
        # In this example, NUM is the sum of A and B
        NUM = A + B
        print(f"Client Request: A={A}, B={B}, NUM={NUM}")
        time.sleep(1)

# Function to update number A
def update_number_A():
    global A
    while True:
        # Simulating API call to update A
        # In this example, A is a random number
        A = random.randint(1, 10)
        print(f"Updated A: {A}")
        time.sleep(5)

# Function to update number B
def update_number_B():
    global B
    while True:
        # Simulating API call to update B
        # In this example, B is fetched from an API
        B = random.randint(1, 10)
        print(f"Updated B: {B}")
        time.sleep(7)

# Creating threads
requests_thread = threading.Thread(target=handle_requests)
update_A_thread = threading.Thread(target=update_number_A)
update_B_thread = threading.Thread(target=update_number_B)

# Starting threads
requests_thread.start()
update_A_thread.start()
update_B_thread.start()

```

### second prompt
> the first thread is responsible for handling incoming requests, you should init a server no matter base on TCP or UDP.
```python=
def handle_client(conn):
    global NUM, A, B
    while True:
        # Simulating client request processing
        # In this example, NUM is the sum of A and B
        result = A + B
        NUM = result
        response = f"A={A}, B={B}, NUM={NUM}"
        conn.send(response.encode())
        time.sleep(1)

# Function to handle incoming client connections
def accept_clients():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("localhost", 8888))
    server_socket.listen(5)
    print("Server started. Listening for incoming connections...")

    while True:
        conn, addr = server_socket.accept()
        print(f"Accepted connection from {addr}")
        client_thread = threading.Thread(target=handle_client, args=(conn,))
        client_thread.start()
```

### Third prompt
> the function handle_client won't leave although it has send response back, you should close the connection automatically.
```python=
def handle_client(conn):
    global NUM, A, B
    # Simulating client request processing
    # In this example, NUM is the sum of A and B
    NUM = A + B
    response = f"A={A}, B={B}, NUM={NUM}"
    conn.send(response.encode())
    time.sleep(1)
    conn.close()

```

### Fourth prompt 
> Although python has GIL, it might still  have concurrent read and write to number A and B due to multi-threading. can you avoid such situation with lock?
```diff 
+ a_lock = threading.Lock()
+ b_lock = threading.Lock()
def handle_client(conn):
    global NUM, A, B
    # Acquire locks before accessing A and B
+   with a_lock:
        a_value = A
+   with b_lock:
        b_value = B

    # Calculate NUM based on A and B
    NUM = a_value + b_value
    response = f"A={a_value}, B={b_value}, NUM={NUM}"
    conn.send(response.encode())
    time.sleep(1)
    conn.close()

def update_number_A():
    global A
    while True:
        # Simulating API call to update A
        # In this example, A is a random number
        new_a = random.randint(1, 10)
+       with a_lock:
            A = new_a
        print(f"Updated A: {A}")
        time.sleep(5)

# Function to update number B
def update_number_B():
    global B
    while True:
        # Simulating API call to update B
        # In this example, B is fetched from an API
        response = requests.get("https://api.example.com/get_b")
        new_b = int(response.json()["value"])
+       with b_lock:
            B = new_b
        print(f"Updated B: {B}")
        time.sleep(7)

```
### Execute result
1. Copy the below final code as `test.py`
2. execute it with `python3 test.py` in terminal.
3. Open another terminal and execute `telnet localhost 8888`
4. See the following result.
![](https://i.imgur.com/wmm6sbY.png)



### Final Code
```python=
import threading
import time
import random
import socket

# Global variables
NUM = 0
A = 0
B = 0

# Locks for synchronization
a_lock = threading.Lock()
b_lock = threading.Lock()

# Function to handle client requests
def handle_client(conn):
    global NUM, A, B

    # Acquire locks before accessing A and B
    with a_lock:
        a_value = A
    with b_lock:
        b_value = B

    # Calculate NUM based on A and B
    NUM = a_value + b_value
    response = f"A={a_value}, B={b_value}, NUM={NUM}"
    conn.send(response.encode())
    time.sleep(1)
    conn.close()

# Function to update number A
def update_number_A():
    global A
    while True:
        # Simulating API call to update A
        # In this example, A is a random number
        new_a = random.randint(1, 10)
        with a_lock:
            A = new_a
        print(f"Updated A: {A}")
        time.sleep(5)

# Function to update number B
def update_number_B():
    global B
    while True:
        # Simulating API call to update B
        # In this example, B is fetched from an API
        new_b = random.randint(1, 10)
        with b_lock:
            B = new_b
        print(f"Updated B: {B}")
        time.sleep(7)

# Function to handle incoming client connections
def accept_clients():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("127.0.0.1", 8888))
    server_socket.listen(5)
    print("Server started. Listening for incoming connections...")

    while True:
        conn, addr = server_socket.accept()
        print(f"Accepted connection from {addr}")
        client_thread = threading.Thread(target=handle_client, args=(conn,))
        client_thread.start()

# Creating threads
client_requests_thread = threading.Thread(target=accept_clients)
update_A_thread = threading.Thread(target=update_number_A)
update_B_thread = threading.Thread(target=update_number_B)

# Starting threads
client_requests_thread.start()
update_A_thread.start()
update_B_thread.start()


```

