Search code examples
pythonmultithreadingprocessmultiprocessingpython-multiprocessing

How to have a thread/process always active and request the updated value of one of its variables every certain time interval in another thread/process


import numpy as np
import cv2

import multiprocessing
import time
import random

finish_state = multiprocessing.Event()

#function that requests frames
def actions_func(frame):
    while True:
        time.sleep(random.randint(1,5))
        cv2.imshow('requested_frame_1',frame)

        time.sleep(random.randint(1,5))
        cv2.imshow('requested_frame_2',frame)

        if cv2.waitKey(1) & 0xFF == ord('q'): break

#function that keeps the camera always on and should return the frame value with the last image only when requested
def capture_cam():
    cap = cv2.VideoCapture(1)

    if (cap.isOpened() == False): 
        print("Unable to read camera feed")

    # Default resolutions of the frame are obtained. The default resolutions are system dependent.
    # We convert the resolutions from float to integer.
    frame_width = int(cap.get(3))
    frame_height = int(cap.get(4))


    while(True):
        ret, frame = cap.read()

        if ret == True: 
            cv2.imshow('frame',frame)

            if cv2.waitKey(1) & 0xFF == ord('q'): break

        else:
            break  


def main_process(finish_state):

    thr1, frame = multiprocessing.Process(target=capture_cam)
    thr1.start()

    thr2 = multiprocessing.Process(target=actions_func, args=(frame,))
    thr2.start()


if __name__ == '__main__':
    main_process(finish_state)

print("continue the code with other things after all threads/processes except the main one were closed with the loop that started them... ")

I want a webcam to be open all the time capturing an image, for this I have created thread1 where it is supposed to run all the time regardless of the program. What you need is to fix this program that is supposed to ask for frames from the function that always runs on thread1. The problem is that I don't know when it might be time to ask thread1 for the last frame it showed, and to represent that I put the random.randint(1,5), although in reality I won't have knowledge of the maximum or minimum time in which the last frame will be requested from thread1

The truth is that I'm getting complicated with this program, and I really don't know if it's convenient to create a thread2 to do the frame requests or if it's better to just have thread1 and have the frame requests inside the main thread

Although they say thread they are actually parallel processes, try with threads but I think it is more convenient to use processes, right?

Traceback (most recent call last):
  File "request_frames_thread.py", line 58, in <module>
    main_process(finish_state)
  File "request_frames_thread.py", line 50, in main_process
    thr1, frame = multiprocessing.Process(target=capture_cam)
TypeError: cannot unpack non-iterable Process object

Solution

  • I would have the main process create a full duplex multiprocessing.Pipe instance which returns two multiprocessing.connection.Connection instances and pass one connection to each of your processes. These connections can be used for a simple two way communication vehicle for sending and receiving objects to one another. I would have the capture_cam process start a dameon thread (it will terminate when all your regular threads terminate and so it can be in an infinite loop) that is passed on of these connections to handle requests for the latest frame, which is stored in a global variable.

    The only requirement is that a frame be serializable by the pickle module.

    import multiprocessing
    from threading import Thread
    import time
    import random
    
    
    #function that requests frames
    def actions_func(conn):
        try:
            while True:
                time.sleep(random.randint(1,5))
                # Ask for latest frame by sending any message:
                conn.send('frame')
                frame = conn.recv() # This is the response
                 cv2.imshow('requested_frame_1',frame)
    
                time.sleep(random.randint(1,5))
                # Ask for latest frame by sending any message:
                conn.send('frame')
                frame = conn.recv() # This is the response
                cv2.imshow('requested_frame_2',frame)
    
                if cv2.waitKey(1) & 0xFF == ord('q'): break
        except BrokenPipeError:
            # The capture_cam process has terminated.
            pass
    
    def handle_frame_requests(conn):
        try:
            while True:
                # Any message coming in is a request for the latest frame:
                request = conn.recv()
                conn.send(frame) # The frame must be pickle-able
        except EOFError:
            # The actions_func process has ended
            # and its connection has been closed.
            pass
    
    #function that keeps the camera always on and should return the frame value with the last image only when requested
    def capture_cam(conn):
        global frame
    
        frame = None
    
        # start dameon thread to handle frame requests:
        Thread(target=handle_frame_requests, args=(conn,), daemon=True).start()
    
        cap = cv2.VideoCapture(1)
    
        if (cap.isOpened() == False):
            print("Unable to read camera feed")
    
        # Default resolutions of the frame are obtained. The default resolutions are system dependent.
        # We convert the resolutions from float to integer.
        frame_width = int(cap.get(3))
        frame_height = int(cap.get(4))
    
    
        while(True):
            ret, frame = cap.read()
    
            if ret == True:
                cv2.imshow('frame',frame)
    
                if cv2.waitKey(1) & 0xFF == ord('q'): break
    
            else:
                break
    
    
    def main_process(finish_state):
        conn1, conn2 = multiprocessing.Pipe(duplex=True)
    
        p1 = multiprocessing.Process(target=capture_cam, args=(conn1,))
        p1.start()
    
        p2 = multiprocessing.Process(target=actions_func, args=(conn2,))
        p2.start()
    
    
    if __name__ == '__main__':
        finish_state = multiprocessing.Event()
        main_process(finish_state)