I would suggest looking at Pools to easily create processes on the fly. If you need to have some shared state, in this case a boolean indicating a non-black image has been found, look at Managers.

Update: Here is an example of what I mean.

import multiprocessing.Manager as Manager
import multiprocessing.Pool as Pool

m = Manager()
p = Pool(processes=5)

state_info = m.dict()
state_info['image_found'] = False

def processImage(img):

    # ... Process Image ...

    if imageIsBlack(img):
        state_info['image_found'] = True
        p.terminate()

 p.apply(processImage, imageList)

 if state_info['image_found']:
     print 'There was a black image!!'
 else:
     print 'No black images were found.'
Answer from owobeid on Stack Overflow
🌐
Medium
medium.com › featurepreneur › speeding-up-image-processing-using-multiprocessing-in-python-ceeced8026bf
Speeding up image processing using multiprocessing in Python | by Santhosh Kannan | featurepreneur | Medium
February 3, 2023 - Multi-processing can be implemented by using the built-in multiprocessing module which contains the methods for creating and working with processes. However, an easier way to start a group of processes is by using the ProcessPoolExecutor which ...
Top answer
1 of 2
2

I would suggest looking at Pools to easily create processes on the fly. If you need to have some shared state, in this case a boolean indicating a non-black image has been found, look at Managers.

Update: Here is an example of what I mean.

import multiprocessing.Manager as Manager
import multiprocessing.Pool as Pool

m = Manager()
p = Pool(processes=5)

state_info = m.dict()
state_info['image_found'] = False

def processImage(img):

    # ... Process Image ...

    if imageIsBlack(img):
        state_info['image_found'] = True
        p.terminate()

 p.apply(processImage, imageList)

 if state_info['image_found']:
     print 'There was a black image!!'
 else:
     print 'No black images were found.'
2 of 2
2

Finally this works nicely for me. Copied it from an example here. For illustration purpose I have replaced my _isImgNonBlack function and the image sequence as a list of 0's and 1's, where 0 being a black image and 1 non-black image.

import multiprocessing

def isImgNonBlack(result_queue, imgSeq):
    for img in imgSeq:
        # If a non-black is found put a result
        if img==1:
            result_queue.put(1)

    # else put a zero as the result
    result_queue.put(0)

if __name__ == '__main__':
    processs = []
    result_queue = multiprocessing.Queue()
    nbProc = 20

    # making a fake list of images with 
    # 10,000 0's follwed by a single 1
    images = [0 for n in range(10000)]
    images.append(1)

    for n in range(nbProc): # start processes crawling for the result
        process = multiprocessing.Process(target=isImgNonBlack, args=[result_queue, images])
        process.start()
        processs.append(process)
        print 'Starting Process : %s' % process

    result = result_queue.get() # waits until any of the proccess have `.put()` a result

    for process in processs: # then kill them all off
        process.terminate()

    # finally print the result
    print "Seq have a non black img: %s" % result
Discussions

Multiprocessing and loading images
Pickle support is more likely to work with Image objects than any of the individual image format types, but I wouldn't recommend it. Multiprocessing is a strange beast in Python, if you can do your processing completely in a separate process it might be worth it, passing back a small result ... More on github.com
🌐 github.com
February 5, 2018
performance - Image processing using multiprocessing in Python - Code Review Stack Exchange
I have an image stack. I am trying to use multiprocessing to do some process on each image in stack to get new image, and then bitwise_or with the old image. Since multiprocessing won't keep the More on codereview.stackexchange.com
🌐 codereview.stackexchange.com
April 20, 2019
python - Multiprocessing on chunks of an image - Stack Overflow
I have a function that has to loop through individual pixels of an image and calculate some geometry. This function takes a very long time to run (~5 hours on a 24 Megapixel image) but seems like it More on stackoverflow.com
🌐 stackoverflow.com
python - OOP multiprocessing on images - Stack Overflow
I want to use multiprocessing to analyze several images in parallel, with my class: class SegmentationType(object): DISPLAY_NAME = "invalid" def __init__(self, filename, path): ... More on stackoverflow.com
🌐 stackoverflow.com
July 8, 2020
🌐
Stack Overflow
stackoverflow.com › questions › 74966222 › using-multiprocessing-to-process-images
python 3.x - Using multiprocessing to process images - Stack Overflow
import os from multiprocessing import Pool import cv2 def resize_image(file_name): # get the name of the file name = os.path.basename(file_name) # just to be sure the file exists (skip if not necessary): if not os.path.exists(name): return f"{name} does not exist!"
🌐
GitHub
github.com › python-pillow › Pillow › issues › 2995
Multiprocessing and loading images · Issue #2995 · python-pillow/Pillow
February 5, 2018 - Multiprocessing is unlikely to be useful if you're passing back full images, as it requires the return values to be pickled, and pickling images then passing them over the socket to unpickle is not a well used path.
🌐
Stack Exchange
codereview.stackexchange.com › questions › 217749 › image-processing-using-multiprocessing-in-python
performance - Image processing using multiprocessing in Python - Code Review Stack Exchange
April 20, 2019 - Later I bitwise_or the new image and old image by a for loop. Is there a way to avoid for-loop for bitwise_or to have a better performance? Thanks · import cv2 as cv import numpy as np from multiprocessing import Pool def process_img(idx, img): new_img = do_something(img) return idx, new_img def main() # ...
🌐
Kaggle
kaggle.com › code › rsmits › using-multiprocessing-for-image-preprocessing
Using multiprocessing for image preprocessing
Checking your browser before accessing www.kaggle.com · Click here if you are not automatically redirected after 5 seconds
🌐
PyImageSearch
pyimagesearch.com › home › blog › multiprocessing with opencv and python
Multiprocessing with OpenCV and Python - PyImageSearch
December 29, 2022 - The payload is assumed to be a Python dictionary but can actually be any datatype provided that we can pickle and unpickle it. Initializes the hashes dictionary (Line 36). Loops over input image paths in the payload (Line 39). In the loop, we load each image, extract the hash, and update hashes dictionary (Lines 41-48). Finally, we write the hashes to disk as a .pickle file (Lines 53-55). For the purposes of this blog post we are utilizing multiprocessing to facilitate faster image hashing of an input dataset; however, you should use this function as a template for your own dataset processing.
Find elsewhere
Top answer
1 of 2
3

I'd do something like this, starting with dependencies:

from multiprocessing import Pool
import numpy as np
from PIL import Image

# and some for testing
from random import random
from time import sleep

first I define a function to divide an image up into "chunks", sort of as you talked about:

def chunkit(ys, xs, blocksize=64):
    for y in range(0, ys, blocksize):
        yt = (y, min(ys, y + blocksize))
        for x in range(0, xs, blocksize):
            xt = (x, min(xs, x + blocksize))
            yield yt, xt

it's a lazy iterator, so this can go on for a while.

I then define my worker function:

def dumb_func(cc):
    (y0,y1), (x0,x1) = cc
    # convert to floats for ease of processing
    chunk = image[y0:y1,x0:x1] / 255.
    # random slow down for testing
    # sleep(random() ** 6)
    res = chunk ** 2
    # convert back to bytes for efficiency
    return cc, (res * 255).astype(np.uint8)

I make sure the source array stays as close to original format as possible for efficiency and send it back in the same format (this might take some fiddling, if you're dealing with other pixel formats obviously).

then I put it together:

if __name__ == '__main__':
    source = Image.open('tmp.jpeg')
    image = np.asarray(source)
    print("loaded", image.shape, image.dtype)

    with Pool() as pool:
        resit = pool.imap_unordered(
            dumb_func, chunkit(*image.shape[:2]))

        output = np.empty_like(image)
        for cc, res in resit:
            (y0,y1), (x0,x1) = cc
            output[y0:y1,x0:x1] = res

    im = Image.fromarray(output, 'RGB')
    im.save('out.jpeg')

this churns through a 15Mpixel image in a couple of seconds, with most of that spent loading/saving the image. it could probably be a lot more clever with array strides and cache friendliness, but hope that helps!

note: I think this code relies on CPython Unix style process forking semantics to make sure the image is shared between processes efficiently. not sure what would happen if you ran it on something else

2 of 2
1

I've been working on code for basically this same thing. Right now the goal is just to replace white pixels with transparent ones, but it seems to replace the entire image so there is a bug somewhere...It doesn't get an error within the multiprocessing module anymore though, so maybe it could serve as an example of how to load a Queue and then have your worker processes work on it!

from PIL import Image
from multiprocessing import Process, JoinableQueue
from threading import Thread
from time import time

def worker_function(q, new_data):
    while True:
        # print("Items in queue: {}".format(q.qsize()))
        index, pixel = q.get()
        if pixel[0] > 240 and pixel[1] > 240 and pixel[2] > 240:
            out_pixel = (0, 0, 0, 0)
        else:
            out_pixel = pixel
        new_data[index] = out_pixel
        q.task_done()

if __name__ == "__main__":
    start = time()
    q = JoinableQueue()

    my_image = Image.open('InputImage.jpg')
    my_image = my_image.convert('RGBA')
    datas = list(my_image.getdata())
    new_data = [0] * len(datas) # make a blank array the size of our image to fill later

    print('putting image into queue')
    for count, item in enumerate(datas):
        q.put((count, item))

    print('starting workers')
    worker_count = 50
    processes = []
    for i in range(worker_count):
        p = Process(target=worker_function, args=[q, new_data])
        p.daemon = True
        p.start()
    print('main thread waiting')
    q.join()
    my_image.putdata(new_data)
    my_image.save('output.png', "PNG")

    end = time()
    print('{:.3f} seconds elapsed'.format(end - start))

I think it's important to "protect" your code inside the if __name__ == "__main__" block otherwise the spawned processes seem to run it.

update

It looks like you need to implement a Manager() (or there are probably other ways I am ignorant of as well!). I got my code to run by altering it into:

from PIL import Image
from multiprocessing import Process, JoinableQueue, Manager
from threading import Thread
from time import time


def worker_function(q, new_data):
    while True:
        # print("Items in queue: {}".format(q.qsize()))
        index, pixel = q.get()
        if pixel[0] > 240 and pixel[1] > 240 and pixel[2] > 240:
            out_pixel = (0, 0, 0, 0)
        else:
            out_pixel = pixel
        new_data[index] = out_pixel
        q.task_done()


if __name__ == "__main__":
    start = time()
    q = JoinableQueue()
    my_image = Image.open('InputImage.jpg')
    my_image = my_image.convert('RGBA')
    datas = list(my_image.getdata())
    # new_data = [(0, 0, 0, 0)]*len(datas)
    manager = Manager()
    new_data = manager.list([(0, 0, 0, 0)]*len(datas))
    print(new_data)
    print('putting image into queue')
    for count, item in enumerate(datas):
        q.put((count, item))

    print('starting workers')
    worker_count = 50
    processes = []
    for i in range(worker_count):
        p = Process(target=worker_function, args=[q, new_data])
        p.daemon = True
        p.start()
    print('main thread waiting')
    q.join()
    print("Saving Image")
    my_image.putdata(new_data)
    my_image.save('output.png', "PNG")

    end = time()
    print('{:.3f} seconds elapsed'.format(end - start))

Although this doesn't seem like the fastest option! I'm sure there are other ways to increase speed. My code to do the same thing with Threads looks VERY similar:

from PIL import Image
from threading import Thread
from queue import Queue
import time

start = time.time()
q = Queue()

planeIm = Image.open('InputImage.jpg')
planeIm = planeIm.convert('RGBA')
datas = planeIm.getdata()
new_data = [0] * len(datas)

print('putting image into queue')
for count, item in enumerate(datas):
    q.put((count, item))

def worker_function():
    while True:
        # print("Items in queue: {}".format(q.qsize()))
        index, pixel = q.get()
        if pixel[0] > 240 and pixel[1] > 240 and pixel[2] > 240:
            out_pixel = (0, 0, 0, 0)
        else:
            out_pixel = pixel
        new_data[index] = out_pixel
        q.task_done()

print('starting workers')
worker_count = 100
for i in range(worker_count):
    t = Thread(target=worker_function)
    t.daemon = True
    t.start()
print('main thread waiting')
q.join()
print('Queue has been joined')
planeIm.putdata(new_data)
planeIm.save('output.png', "PNG")

end = time.time()

elapsed = end - start
print('{:3.3} seconds elapsed'.format(elapsed))

Yet, processing my image takes ~23 seconds with threads and ~170 seconds with multiprocessing!! I suspect this would come from the larger overhead needed to start Process objects, and the fact that my algorithm for processing each pixel is simple for now (just the if pixel[0] > 240 and pixel[1] > 240 and pixel[2] > 240: bit), so I'm likely not yielding the speed improvements that a complex pixel processing algorithm would get me. Also to note multiprocessing documentation

a single manager can be shared by processes on different computers over a network. They are, however, slower than using shared memory.

Which leads me to believe that there are alternatives that are faster.

🌐
GeeksforGeeks
geeksforgeeks.org › python › how-can-we-display-an-image-in-a-child-process-in-python
How can we display an image in a child process in Python - GeeksforGeeks
July 10, 2024 - This child process communicates back to the main process using a Queue (queue.put) to signal when the image is displayed, ensuring synchronization (queue.get) before joining (p.join()) the child process. ... import multiprocessing as mp import tkinter as tk from PIL import Image, ImageTk def approach1Fn(image_path, queue): root = tk.Tk() root.title("Image Display") image = Image.open(image_path) photo = ImageTk.PhotoImage(image) label = tk.Label(root, image=photo) label.pack() queue.put("Image displayed") root.mainloop() if __name__ == "__main__": image_path = "gfglogo.png" queue = mp.Queue() p = mp.Process(target=approach1Fn, args=(image_path, queue)) p.start() message = queue.get() print(message) p.join()
🌐
Python Tutorial
pythontutorial.net › home › python concurrency › python multiprocessing
Python Multiprocessing
June 2, 2023 - We’ll use the multiprocessing module to resize the high-resolution images.
Top answer
1 of 1
2

You have to create list with pairs (filename, path)

data = [(img, img_dir) for img in images]

and then map will run every pair in separated process.

But you have to get args in start_pipeline

    def start_pipeline(self, args):
        print('ok starting')
        
        filename, path = args
        print('filename: {}\npath: {}'.format(filename, path))
        
        return self.process()

And you have to use () to create instance of class segmenter_class to use start_pipeline

pool.map(segmenter_class().start_pipeline, data)

BTW: In example code I also return result from process.


import os
import multiprocessing

class SegmentationType(object):
    DISPLAY_NAME = "invalid"

    def __init__(self):
        print('init')

    def read_image(self):
        print('read')

    def write_image(self):
        print('write')

    def process(self):
        # override in derived classes to perform an actual segmentation
        pass

    def start_pipeline(self, args):
        print('ok starting')
        
        filename, path = args
        print('filename: {}\npath: {}'.format(filename, path))
        
        return self.process()

class HSV_Segmenter(SegmentationType):
    DISPLAY_NAME = 'HSV'

    def process(self):
        print('ok HSV')
        return "result HSV"
    
class LabSegmenter(SegmentationType):
    DISPLAY_NAME = 'LAB'

    def process(self):
        print('ok LAB')
        return "result LAB"

if __name__ == '__main__':

    procedure = 'hsv'
    
    segmenter_class = {
        'hsv': HSV_Segmenter,
        'lab': LabSegmenter,
    }.get(procedure)
    
    images = ['01.png', '02.png', '03.png']
    img_dir = 'C:/'
    
    data = [(img, img_dir) for img in images]
    
    pool = multiprocessing.Pool(3)

    # example 1

    results = pool.map(segmenter_class().start_pipeline, data)
    print('Results:', results)

    # example 2

    for result in pool.map(segmenter_class().start_pipeline, data):
        print('result:', result)

    pool.terminate()

EDIT:

You can also create function which gets procedure and data and then use it in map - this way every process will create own instance of procedure or you can send different procedures to different processes.

import os
import multiprocessing

class SegmentationType(object):
    DISPLAY_NAME = "invalid"

    def __init__(self):
        print('init')

    def read_image(self):
        print('read')

    def write_image(self):
        print('write')

    def process(self):
        # override in derived classes to perform an actual segmentation
        pass

    def start_pipeline(self, args):
        print('ok starting')
        
        filename, path = args
        print('filename: {}\npath: {}'.format(filename, path))
        
        return self.process()

class HSV_Segmenter(SegmentationType):
    DISPLAY_NAME = 'HSV'

    def process(self):
        print('ok HSV')
        return "result HSV"
    
class LabSegmenter(SegmentationType):
    DISPLAY_NAME = 'LAB'

    def process(self):
        print('ok LAB')
        return "result LAB"

segmenters = {
    'hsv': HSV_Segmenter,
    'lab': LabSegmenter,
}

def start_process(args):

    procedure = args[0]
    data = args[1:]

    segmenter_class = segmenters.get(procedure)
    result = segmenter_class().start_pipeline(data)

    return result
    
if __name__ == '__main__':

    procedure = 'hsv'
    
    images = ['01.png', '02.png', '03.png']
    img_dir = 'C:/'
    
    data = [(procedure, img, img_dir) for img in images]
    
    pool = multiprocessing.Pool(3)

    # example 1

    results = pool.map(start_process, data)
    print('Results:', results)

    # example 2

    for result in pool.map(segmenter_class().start_pipeline, data):
        print('result:', result)

    pool.terminate()

Example with different procedures

if __name__ == '__main__':

    images = ['01.png', '02.png', '03.png']
    img_dir = 'C:/'
    
    pool = multiprocessing.Pool(3)

    data = [('hsv', img, img_dir) for img in images]
    results = pool.map(start_process, data)
    print('Results HSV:', results)

    data = [('lab', img, img_dir) for img in images]
    results = pool.map(start_process, data)
    print('Results LAB:', results)

    pool.terminate()

And the same with one map(). There are 6 processes to start and Pool(3) so it will run only 3 process at the same time and when it will have free process then map will get next values from list and run process.

if __name__ == '__main__':

    images = ['01.png', '02.png', '03.png']
    img_dir = 'C:/'
    
    data_hsv = [('hsv', img, img_dir) for img in images]
    data_lab = [('lab', img, img_dir) for img in images]
    
    data = data_hsv + data_lab

    pool = multiprocessing.Pool(3)

    # example 1

    results = pool.map(start_process, data)
    print('Results:', results)

    # example 2

    for result in pool.map(start_process, data):
        print('results:', result)

    pool.terminate()

EDIT:

It works also with Ray

It needs only

from ray.util import multiprocessing

instead of

import multiprocessing

I didn't tested it with Dask, PySpark or Joblib


EDIT:

Example with Joblib

from joblib import Parallel, delayed

class SegmentationType(object):
    DISPLAY_NAME = "invalid"

    def __init__(self):
        print('init')

    def read_image(self):
        print('read')

    def write_image(self):
        print('write')

    def process(self):
        # override in derived classes to perform an actual segmentation
        pass

    def start_pipeline(self, args):
        print('ok starting')
        
        filename, path = args
        print('filename: {}\npath: {}'.format(filename, path))
        
        return self.process()

class HSV_Segmenter(SegmentationType):
    DISPLAY_NAME = 'HSV'

    def process(self):
        print('ok HSV')
        return "result HSV"

class LabSegmenter(SegmentationType):
    DISPLAY_NAME = 'LAB'

    def process(self):
        print('ok LAB')
        return "result LAB"

segmenters = {
    'hsv': HSV_Segmenter,
    'lab': LabSegmenter,
}

def start_process(args):
    
    procedure = args[0]
    data = args[1:]
    
    segmenter_class = segmenters.get(procedure)
    result = segmenter_class().start_pipeline(data)
    
    return result

if __name__ == '__main__':

    images = ['01.png', '02.png', '03.png']
    img_dir = 'C:/'
    
    data_hsv = [('hsv', img, img_dir) for img in images]
    data_lab = [('lab', img, img_dir) for img in images]
    
    data = data_hsv + data_lab

    # --- version 1 ---

    #pool = Parallel(n_jobs=3, backend='threading')
    #pool = Parallel(n_jobs=3, backend='multiprocessing')
    pool = Parallel(n_jobs=3)
    
    # example 1
    
    results = pool( delayed(start_process)(args) for args in data )
    print('Results:', results)

    # example 2
    
    for result in pool( delayed(start_process)(args) for args in data ):
        print('result:', result)

    # --- version 2 ---
    
    #with Parallel(n_jobs=3, backend='threading') as pool:
    #with Parallel(n_jobs=3, backend='multiprocessing') as pool:
    with Parallel(n_jobs=3) as pool:

        # example 1
        
        results = pool( delayed(start_process)(args) for args in data )
        print('Results:', results)

        # example 1

        for result in pool( delayed(start_process)(args) for args in data ):
            print('result:', result)
        
🌐
DEV Community
dev.to › tecnosam › multiprocessing-in-python-part-2-584g
Multiprocessing in Python (Part 2) - DEV Community
August 15, 2022 - We will be using multiprocessing for the CPU-intensive function, which is the function that detects and draws rectangles on the faces. from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor def optimised_solution(image_paths: list): # Define our pool executors with ThreadPoolExecutor() as thread_pool: with ProcessPoolExecutor() as process_pool: # We use threading to read our images images = thread_pool.map(read_image, image_paths) # We use multiprocessing to process our images processed_images = process_pool.map(draw_face,images) # We use multithreadfing to write our images to
🌐
Stack Overflow
stackoverflow.com › questions › 67761288 › using-multiprocessing-on-image-processing
python - Using multiprocessing on Image processing - Stack Overflow
Now I wanted to parallelize this function, since f.e. each row of such image parts could be dealt with entirely on its own. I came up with this approach using multiprocessing.Pool:
🌐
Reddit
reddit.com › r/learnpython › multiprocessing with image in shared memory
r/learnpython on Reddit: multiprocessing with image in shared memory
February 2, 2018 -

So I am working on a script that is reading from a camera with opencv and I have multiple processes. One process is reading the camera and I have been able to share frame between them using a pipe but I need to do it faster. I found this posting but some of the answers are missing:

https://www.reddit.com/r/learnpython/comments/4u0xh8/fastest_way_to_share_numpy_arrays_between/

Can someone give me an example of what this means?

You would simply use ctypes to find this structure underlying a PhotoImage object, update its pixelPtr to point to the numpy array and adjust the pitch, pixelSize and offset to match your format of color data.

🌐
Better Programming
betterprogramming.pub › building-an-imagedownloader-with-multiprocessing-in-python-44aee36e0424
Building an Image Downloader With Multiprocessing in Python | by Amit Upreti | Better Programming
November 12, 2019 - Building an Image Downloader With Multiprocessing in Python Sometimes it takes hours to download a large number of images — let’s fix that I get it — you’re tired of waiting for your program …
Top answer
1 of 2
4

The error you're seeing is because you're calling a method that doesn't exist, probably because it's not part of self.

You'll likely see better performance with multithreading than multiprocessing. Multiprocessing is best for CPU-bound tasks for the simple reason that Python uses a global lock for all operations. Multiprocessing is a hack to get around this lock. It's nastier to work with than threading and it's best to avoid it unless absolutely necessary.

Multithreading is likely enough for your use case and it won't create lots of gotchas for a new programmer. Here's a working sample setup using Python 3's Futures API that will easily scale your problem size, just add your arguments and actual save code in the marked places.

import concurrent.futures

# Save single image
def save_image(image_arg):
    # your image save code goes here
    print("Working on image {}...".format(image_arg))
    return True

# max_workers specifies the number of threads. If None then use 5x your CPU count
with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor:
    # Images we'll save. Depending on how you generate your images you might not
    # want to materialize a list like this to avoid running out of memory.
    image_args = ["image1", "image2", "image3"]

    # Submit futures to the executor pool.
    # Map each future back to the arguments used to create that future. That way
    # if one fails we know which image it was that failed.
    future_to_args = {executor.submit(save_image, image_arg): image_arg for image_arg in image_args}

    # Images are being saved in worker threads. They will complete in any order.
    for future in concurrent.futures.as_completed(future_to_args):
        image_arg = future_to_args[future]
        try:
            result = future.result()
        except Exception as exc:
            print("Saving image {} generated an exception: {}".format(image_arg, exc))
        else:
            print("Image {} saved successfully.".format(image_arg))

If you insist on multiprocessing, just use ProcessPoolExecutor instead. That might be worthwhile if you also want to generate your images in parallel.

Whether ThreadPoolExecutor or ProcessPoolExecutor is better depends a lot on what the rest of your workload is and how you structured it. Try both to see which works better for you. Note that multiprocessing places restrictions on communication and sharing state between workers, hence why I suggest trying threads first.

2 of 2
1

Before you try to improve, you should always measure performance.

Use a disk testing program to see what the maximum sustained write throughput of your disk is.

Then use a performance monitoring program to check the write throughput that your program generates (without multi- threading/processing). If your program can reach the same throughput as the test program most of the time, then there is little you can do.

Assuming that you are using a regular harddisk, the best way to improve write performance is to use an SSD instead.

🌐
Towards Data Science
towardsdatascience.com › home › latest › applying python multiprocessing in 2 lines of code
Applying Python multiprocessing in 2 lines of code | Towards Data Science
March 5, 2025 - We receive 12 images from our clients; they are all between 0.2 and 2 MB. I've stored all paths to the images in a string-array like below. Before we apply multiprocessing to our function we'll run the get_most_popular_color() function normally so we'll have something to compare against.
🌐
Toptal
toptal.com › python › beginners-guide-to-concurrency-and-parallelism-in-python
Python Multithreading and Multiprocessing Tutorial | Toptal®
May 26, 2026 - The Python multiprocessing module is easier to drop in than the threading module, as we don’t need to add a class like the Python multithreading example. The only changes we need to make are in the main function. To use multiple processes, we create a multiprocessing Pool. With the map method it provides, we will pass the list of URLs to the pool, which in turn will spawn eight new processes and use each one to download the images in parallel.