# 30. Multiprocessing Module

This Python snippets demonstrating the use of the `multiprocessing` module to run parallel processes for CPU-bound tasks:

***

**1. Basic Multiprocessing Example**

```python
from multiprocessing import Process

def print_numbers():
    for i in range(5):
        print(f"Process: {i}")

if __name__ == "__main__":
    process = Process(target=print_numbers)
    process.start()
    process.join()
```

***

**2. Passing Arguments to a Process**

```python
from multiprocessing import Process

def print_range(start, end):
    for i in range(start, end):
        print(f"Range {start}-{end}: {i}")

if __name__ == "__main__":
    process = Process(target=print_range, args=(1, 6))
    process.start()
    process.join()
```

***

**3. Using a Pool of Processes**

```python
from multiprocessing import Pool

def square(n):
    return n * n

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        results = pool.map(square, [1, 2, 3, 4, 5])
        print(results)
```

***

**4. Process Synchronization with Lock**

```python
from multiprocessing import Process, Lock

def print_with_lock(lock, value):
    with lock:
        print(f"Value: {value}")

if __name__ == "__main__":
    lock = Lock()
    processes = [Process(target=print_with_lock, args=(lock, i)) for i in range(5)]
    
    for p in processes:
        p.start()
    for p in processes:
        p.join()
```

***

**5. Sharing Data with `Value` and `Array`**

```python
from multiprocessing import Process, Value, Array

def increment(shared_value, shared_array):
    shared_value.value += 1
    for i in range(len(shared_array)):
        shared_array[i] += 1

if __name__ == "__main__":
    shared_value = Value('i', 0)  # Integer
    shared_array = Array('i', [0, 1, 2, 3])

    processes = [Process(target=increment, args=(shared_value, shared_array)) for _ in range(5)]
    
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print("Shared Value:", shared_value.value)
    print("Shared Array:", list(shared_array))
```

***

**6. Using a Queue for Process Communication**

```python
from multiprocessing import Process, Queue

def producer(queue):
    for i in range(5):
        queue.put(i)
        print(f"Produced: {i}")

def consumer(queue):
    while not queue.empty():
        item = queue.get()
        print(f"Consumed: {item}")

if __name__ == "__main__":
    queue = Queue()
    producer_process = Process(target=producer, args=(queue,))
    consumer_process = Process(target=consumer, args=(queue,))

    producer_process.start()
    producer_process.join()

    consumer_process.start()
    consumer_process.join()
```

***

**7. Using Manager for Shared State**

```python
from multiprocessing import Process, Manager

def append_to_list(shared_list):
    shared_list.append("Item")

if __name__ == "__main__":
    with Manager() as manager:
        shared_list = manager.list()
        processes = [Process(target=append_to_list, args=(shared_list,)) for _ in range(5)]

        for p in processes:
            p.start()
        for p in processes:
            p.join()

        print("Shared List:", list(shared_list))
```

***

**8. Using `Pool.apply_async` for Asynchronous Processing**

```python
from multiprocessing import Pool

def square(n):
    return n * n

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        result = pool.apply_async(square, args=(5,))
        print("Square:", result.get())
```

***

**9. Using `Process` with Daemon**

```python
from multiprocessing import Process
import time

def background_task():
    while True:
        print("Daemon process running...")
        time.sleep(1)

if __name__ == "__main__":
    daemon_process = Process(target=background_task, daemon=True)
    daemon_process.start()

    time.sleep(3)
    print("Main process ends.")
```

***

**10. Using Barrier for Synchronization**

```python
from multiprocessing import Process, Barrier

def process_task(barrier, process_id):
    print(f"Process-{process_id} waiting at barrier")
    barrier.wait()
    print(f"Process-{process_id} passed the barrier")

if __name__ == "__main__":
    barrier = Barrier(3)
    processes = [Process(target=process_task, args=(barrier, i)) for i in range(3)]

    for p in processes:
        p.start()
    for p in processes:
        p.join()
```

***

These examples cover the basics of multiprocessing, including communication, synchronization, data sharing, process pools, and daemon processes.
