Skip to content

Commit 454c5b2

Browse files
Initial Commit
1 parent fde5c69 commit 454c5b2

20 files changed

+943
-0
lines changed

1) Multithreading/README.md

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Multithreading in Python: A Simple Guide
2+
3+
## What is Multithreading?
4+
5+
Multithreading allows you to run multiple parts of your program simultaneously. In Python, this means you can have different tasks running at the same time. Multithreading is particularly useful when you have IO-bound tasks like reading/writing files or making network requests. However, for CPU-bound tasks, multithreading in Python might not provide much benefit due to the Global Interpreter Lock (GIL).
6+
7+
## Why Use Multithreading?
8+
9+
- **Faster Execution**: For tasks that can run concurrently (like waiting for a file to load), multithreading can improve the program's speed.
10+
- **Better Resource Utilization**: Multiple threads can use system resources efficiently, allowing your program to do multiple things at once.
11+
12+
### Key Libraries:
13+
- `threading`: Provides low-level thread control.
14+
- `concurrent.futures`: A high-level interface for running tasks concurrently.
15+
16+
---
17+
18+
## Using `threading` in Python
19+
20+
The `threading` library allows you to create and manage threads. Each thread runs a function concurrently.
21+
22+
### Example 1: Using `threading`
23+
24+
```python
25+
import threading
26+
import time
27+
28+
def print_numbers():
29+
for i in range(5):
30+
print(f"Number: {i}")
31+
time.sleep(1)
32+
33+
def print_letters():
34+
for letter in "ABCDE":
35+
print(f"Letter: {letter}")
36+
time.sleep(1)
37+
38+
# Create threads
39+
thread1 = threading.Thread(target=print_numbers)
40+
thread2 = threading.Thread(target=print_letters)
41+
42+
# Start threads
43+
thread1.start()
44+
thread2.start()
45+
46+
# Wait for both threads to finish
47+
thread1.join()
48+
thread2.join()
49+
50+
print("Both threads have finished execution.")
51+
```
52+
53+
### Explanation:
54+
- `threading.Thread`: Creates a new thread that runs a specified function.
55+
- `.start()`: Starts the thread.
56+
- `.join()`: Waits for the thread to complete before continuing.
57+
58+
In this example, the two threads will print numbers and letters concurrently.
59+
60+
---
61+
62+
## Using `concurrent.futures` for Multithreading
63+
64+
The `concurrent.futures` library provides a simpler and higher-level interface for handling threading and multiprocessing. It uses thread pools to manage multiple threads.
65+
66+
### Example 2: Using `concurrent.futures.ThreadPoolExecutor`
67+
68+
```python
69+
from concurrent.futures import ThreadPoolExecutor
70+
import time
71+
72+
def task(name, duration):
73+
print(f"Task {name} starting.")
74+
time.sleep(duration)
75+
print(f"Task {name} completed after {duration} seconds.")
76+
77+
# Create a ThreadPoolExecutor
78+
with ThreadPoolExecutor(max_workers=2) as executor:
79+
executor.submit(task, 'A', 2)
80+
executor.submit(task, 'B', 3)
81+
82+
print("All tasks are submitted.")
83+
```
84+
85+
### Explanation:
86+
- `ThreadPoolExecutor`: Manages a pool of worker threads that execute tasks concurrently.
87+
- `.submit()`: Schedules a function to be executed by the thread pool.
88+
- `max_workers`: Defines the maximum number of threads that can run at the same time.
89+
90+
In this example, the `ThreadPoolExecutor` runs two tasks (`task 'A'` and `task 'B'`) concurrently.
91+
92+
---
93+
94+
## When to Use `threading` vs `concurrent.futures`?
95+
96+
- Use `threading` when you need more control over each thread (e.g., starting, stopping, or managing multiple threads manually).
97+
- Use `concurrent.futures` when you want a simpler, high-level interface for managing multiple tasks concurrently.
98+
99+
---
100+
101+
## Key Considerations:
102+
- **Global Interpreter Lock (GIL)**: Python threads are limited by the GIL, meaning only one thread executes Python bytecode at a time. For CPU-bound tasks, consider using `concurrent.futures.ProcessPoolExecutor` or multiprocessing.
103+
- **Use Cases**: Multithreading is great for IO-bound tasks like downloading files, web scraping, or handling database connections.
104+
105+
---
106+
107+
### Further Reading:
108+
- [Python threading documentation](https://docs.python.org/3/library/threading.html)
109+
- [concurrent.futures documentation](https://docs.python.org/3/library/concurrent.futures.html)
110+
111+
Note:- It is rephrased by ChatGPT 4o
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import threading
2+
import time
3+
4+
def let_sleep(sec):
5+
print(f"Program is sleep for {sec} seconds")
6+
time.sleep(sec)
7+
print(f"Program waked up after {sec} seconds")
8+
9+
start_t=time.time()
10+
let_sleep(4)
11+
let_sleep(3)
12+
let_sleep(2)
13+
end_t=time.time()
14+
print(f"Time taken to complete above process is {end_t-start_t} sec")
15+
# -> This will take nearly 9 secs to complete
16+
17+
18+
start_t=time.time()
19+
t1=threading.Thread(target=let_sleep, args=[4])
20+
t2=threading.Thread(target=let_sleep, args=[3])
21+
t3=threading.Thread(target=let_sleep, args=[2])
22+
23+
#--> t.start():- this will start the process and send it to the backend
24+
25+
t1.start()
26+
t2.start()
27+
t3.start()
28+
29+
#--> t.join():- This will tell that wait for threads to complete it's execution
30+
31+
t1.join()
32+
t2.join()
33+
t3.join()
34+
end_t=time.time()
35+
36+
print(f"Time taken to complete above process is {end_t-start_t} sec")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from concurrent.futures import ThreadPoolExecutor
2+
import time
3+
4+
def let_sleep(sec):
5+
print(f"Program is sleep for {sec} seconds")
6+
time.sleep(sec)
7+
print(f"Program waked up after {sec} seconds")
8+
9+
#--> Method 1 :- Creating individual threads in threadpool
10+
11+
def pooling():
12+
with ThreadPoolExecutor(max_workers=4) as executor:
13+
f1=executor.submit(let_sleep,4)
14+
f2=executor.submit(let_sleep,3)
15+
f3=executor.submit(let_sleep,2)
16+
f4=executor.submit(let_sleep,3)
17+
print(f1.result())
18+
print(f2.result())
19+
print(f3.result())
20+
print(f4.result())
21+
22+
23+
#--> Method 2 :- Using map or iterator
24+
25+
def pooling_map():
26+
with ThreadPoolExecutor(max_workers=4) as executor:
27+
ls=[4,3,2,3]
28+
results=executor.map(let_sleep,ls)
29+
for r in results:
30+
print(r)
31+
32+
pooling_map()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from concurrent.futures import ThreadPoolExecutor
2+
import os
3+
import requests
4+
import time
5+
6+
img_file_seq='img_src_seq'
7+
img_file_mt='img_src_mt'
8+
9+
#--> small height and width img
10+
# url='https://picsum.photos/200/300'
11+
12+
#--> big height and width img
13+
url='https://picsum.photos/2000/3000'
14+
15+
def create_folder(name):
16+
if not os.path.exists(name):
17+
os.makedirs(name)
18+
return name
19+
20+
def download_img(links, file_name):
21+
response=requests.get(links)
22+
if response.status_code == 200:
23+
with open(file_name,'wb') as f:
24+
f.write(response.content)
25+
print(f"{os.path.basename(file_name)} Downloaded! ")
26+
else:
27+
print(f"Failed to download the {file_name}. Status code:- {response.status_code}")
28+
print()
29+
30+
def download_img_seq(img_file, img_num):
31+
folder=create_folder(img_file)
32+
33+
for i in range(img_num):
34+
image_name=f"img-{i+1}.jpg"
35+
file_name=os.path.join(folder,image_name)
36+
download_img(url,file_name)
37+
38+
def download_img_multithreading(img_file, img_num, num_threads=5):
39+
folder=create_folder(img_file)
40+
41+
with ThreadPoolExecutor(max_workers=num_threads) as executor:
42+
futures = [executor.submit(download_img, url, os.path.join(folder, f"image_threaded_{i + 1}.jpg")) for i in range(img_num)]
43+
# for future in futures:
44+
# print(future.result())
45+
46+
def func_run(func, img_file, img_num, tag):
47+
start_t=time.time()
48+
func(img_file, img_num)
49+
total_t=time.time()-start_t
50+
print(f"Total time take to download {img_num} images is {total_t: .2f} seconds for {tag}")
51+
52+
if __name__ == "__main__":
53+
img_num=5
54+
func_run(download_img_seq,img_file_seq,img_num, "sequential method")
55+
56+
func_run(download_img_multithreading,img_file_mt,img_num, "multithreading method")

2) Multicore Processing/README.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Multicore Processing in Python: A Simple Guide
2+
3+
## What is Multicore Processing?
4+
5+
Multicore processing allows programs to run on multiple CPU cores simultaneously. This is especially useful for CPU-bound tasks that require a lot of computation, like data processing, mathematical calculations, or machine learning.
6+
7+
Unlike multithreading (which is limited by Python’s Global Interpreter Lock or GIL), multiprocessing runs separate processes, each with its own Python interpreter, which allows full utilization of multiple cores.
8+
9+
## Why Use Multicore Processing?
10+
11+
- **Parallelism**: Multicore processing allows for true parallel execution, speeding up CPU-intensive tasks.
12+
- **No GIL Limitation**: Since each process has its own Python interpreter, it avoids the GIL, allowing multiple cores to run Python code in parallel.
13+
14+
### Key Libraries:
15+
- `multiprocessing`: Provides tools for spawning multiple processes and managing shared data between them.
16+
- `concurrent.futures`: A high-level interface that can also be used for multiprocessing via `ProcessPoolExecutor`.
17+
18+
---
19+
20+
## Using `multiprocessing` in Python
21+
22+
The `multiprocessing` library allows you to create and manage multiple processes. Each process runs independently, and they can run on different CPU cores.
23+
24+
### Example 1: Using `multiprocessing`
25+
26+
```python
27+
import multiprocessing
28+
import time
29+
30+
def square_number(number):
31+
print(f"Square of {number} is {number * number}")
32+
time.sleep(1)
33+
34+
if __name__ == "__main__":
35+
processes = []
36+
numbers = [1, 2, 3, 4, 5]
37+
38+
# Create a process for each number
39+
for number in numbers:
40+
process = multiprocessing.Process(target=square_number, args=(number,))
41+
processes.append(process)
42+
process.start()
43+
44+
# Wait for all processes to complete
45+
for process in processes:
46+
process.join()
47+
48+
print("All processes have finished execution.")
49+
```
50+
51+
### Explanation:
52+
- `multiprocessing.Process`: Creates a new process.
53+
- `.start()`: Starts the process.
54+
- `.join()`: Waits for the process to complete before moving on.
55+
- `if __name__ == "__main__"`: Ensures that the code block is only run in the main process (important for Windows systems).
56+
57+
In this example, each number is processed by a separate CPU core, and the squares are calculated in parallel.
58+
59+
---
60+
61+
## Using `concurrent.futures` for Multiprocessing
62+
63+
The `concurrent.futures` library can also be used for multiprocessing through `ProcessPoolExecutor`. This provides a simpler, high-level API for multicore processing.
64+
65+
### Example 2: Using `concurrent.futures.ProcessPoolExecutor`
66+
67+
```python
68+
from concurrent.futures import ProcessPoolExecutor
69+
import time
70+
71+
def cube_number(number):
72+
print(f"Cube of {number} is {number * number * number}")
73+
time.sleep(1)
74+
75+
numbers = [1, 2, 3, 4, 5]
76+
77+
# Create a ProcessPoolExecutor
78+
with ProcessPoolExecutor() as executor:
79+
executor.map(cube_number, numbers)
80+
81+
print("All tasks are completed.")
82+
```
83+
84+
### Explanation:
85+
- `ProcessPoolExecutor`: Manages a pool of worker processes that execute tasks concurrently.
86+
- `.map()`: Distributes the function (`cube_number`) across the process pool for all the numbers in the list.
87+
88+
In this example, the `ProcessPoolExecutor` runs multiple processes concurrently, calculating the cube of each number in parallel.
89+
90+
---
91+
92+
## When to Use `multiprocessing` vs `concurrent.futures`?
93+
94+
- Use `multiprocessing` when you need more control over each process (e.g., starting, stopping, or managing multiple processes manually).
95+
- Use `concurrent.futures.ProcessPoolExecutor` when you want a simpler, high-level interface for managing multiple CPU-bound tasks concurrently.
96+
97+
---
98+
99+
## Key Considerations:
100+
- **Inter-Process Communication (IPC)**: Since each process runs independently, sharing data between processes can be done via `multiprocessing.Queue` or `multiprocessing.Pipe`.
101+
- **Use Cases**: Multicore processing is ideal for CPU-bound tasks like numerical computations, data analysis, and heavy processing jobs that require multiple CPU cores.
102+
103+
---
104+
105+
## Performance Considerations:
106+
- **Overhead**: Starting new processes has more overhead than starting new threads, so multicore processing is best suited for long-running, CPU-bound tasks.
107+
- **Shared Memory**: By default, each process has its own memory space, which means you need to use specific techniques like `multiprocessing.Manager` for shared data.
108+
109+
---
110+
111+
### Further Reading:
112+
- [Python multiprocessing documentation](https://docs.python.org/3/library/multiprocessing.html)
113+
- [concurrent.futures documentation](https://docs.python.org/3/library/concurrent.futures.html)
114+
115+
Note:- It is rephrased by ChatGPT 4o
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import multiprocessing
2+
import time
3+
import utils
4+
import os
5+
6+
7+
if __name__=="__main__":
8+
img_file='img_src'
9+
img_num=5
10+
start_t=time.time()
11+
utils.download_img_seq(img_file, img_num)
12+
int_t=time.time()
13+
print(f"Time taken for single core to download {img_num} images is {int_t-start_t} sec")
14+
15+
start_t=time.time()
16+
processes=[]
17+
for i in range(img_num):
18+
img_file=utils.create_folder(img_file)
19+
file_name=os.path.join(img_file,f"img-mc-{i+1}.jpg")
20+
21+
p=multiprocessing.Process(target=utils.download_img, args=[utils.url,file_name])
22+
p.start()
23+
processes.append(p)
24+
25+
for p in processes:
26+
p.join()
27+
end_t=time.time()
28+
print(f"Time taken for multicore to download {img_num} images is {end_t-start_t} sec")

0 commit comments

Comments
 (0)