I have 2 separate processes using QSharedMemory for IPC. One of them should have a thread waiting for the other process to write to this shared memory segment and once that happens perform an action (all of this without blocking the main thread).
The file consumer.py:
import time
import threading
from PyQt5.QtCore import QSystemSemaphore, QSharedMemory
sem = QSystemSemaphore("test-sem")
shmem = QSharedMemory("test-shmem")
shmem.create(64, mode=QSharedMemory.ReadWrite)
def wait_for_release():
while True:
sem.acquire()
shmem.lock()
data = shmem.data()
data_str = str(data, encoding="utf-8")
data_str = data_str.rstrip("\x00")
shmem.unlock()
print(f"Hello {data_str}!")
threading.Thread(target=wait_for_release).start()
# This should be running while the other thread waits for the semaphore to be released
while True:
print("Waiting...")
time.sleep(1)
producer.py
from PyQt5.QtCore import QSystemSemaphore, QSharedMemory, QByteArray
sem = QSystemSemaphore("test-sem")
shmem = QSharedMemory("test-shmem")
if shmem.attach(QSharedMemory.ReadWrite):
str_bytes = QByteArray("World".encode("ascii"))
shmem.lock()
shmem.data()[:str_bytes.size()] = str_bytes
# Release so the other process can print "Hello World!"
sem.release()
The thing I expected to happen when I start running consumer.py is the main thread printing "Waiting..." every second and once i run producer.py printing "Hello World!" the main thread blocks when the semaphore (which is running in a separate thread) blocks. Does QSystemSemaphore block the whole process? Is there another alternative?
Related
I'm trying to launch a function (my_function) and stop its execution after a certain time is reached.
So i challenged multiprocessing library and everything works well. Here is the code, where my_function() has been changed to only create a dummy message.
from multiprocessing import Queue, Process
from multiprocessing.queues import Empty
import time
timeout=1
# timeout=3
def my_function(something):
time.sleep(2)
return f'my message: {something}'
def wrapper(something, queue):
message ="too late..."
try:
message = my_function(something)
return message
finally:
queue.put(message)
try:
queue = Queue()
params = ("hello", queue)
child_process = Process(target=wrapper, args=params)
child_process.start()
output = queue.get(timeout=timeout)
print(f"ok: {output}")
except Empty:
timeout_message = f"Timeout {timeout}s reached"
print(timeout_message)
finally:
if 'child_process' in locals():
child_process.kill()
You can test and verify that depending on timeout=1 or timeout=3, i can trigger an error or not.
My main problem is that the real my_function() is a torch model inference for which i would like to limit the number of threads (to 4 let's say)
One can easily do so if my_function were in the main process, but in my example i tried a lot of tricks to limit it in the child process without any success (using threadpoolctl.threadpool_limits(4), torch.set_num_threads(4), os.environ["OMP_NUM_THREADS"]=4, os.environ["MKL_NUM_THREADS"]=4).
I'm completely open to other solution that can monitor the time execution of a function while limiting the number of threads used by this function.
thanks
Regards
You can limit simultaneous process with Pool. (https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool)
You can set max tasks done per child. Check it out.
Here you have a sample from superfastpython by Jason Brownlee:
# SuperFastPython.com
# example of limiting the number of tasks per child in the process pool
from time import sleep
from multiprocessing.pool import Pool
from multiprocessing import current_process
# task executed in a worker process
def task(value):
# get the current process
process = current_process()
# report a message
print(f'Worker is {process.name} with {value}', flush=True)
# block for a moment
sleep(1)
# protect the entry point
if __name__ == '__main__':
# create and configure the process pool
with Pool(2, maxtasksperchild=3) as pool:
# issue tasks to the process pool
for i in range(10):
pool.apply_async(task, args=(i,))
# close the process pool
pool.close()
# wait for all tasks to complete
pool.join()
I'm trying to code a kind of task manager in Python. It's based on a job queue, the main thread is in charge of adding jobs to this queue. I have made this class to handle the jobs queued, able to limit the number of concurrent processes and handle the output of the finished processes.
Here comes the problem, the _check_jobs function I don't get updated the returncode value of each process, independently of its status (running, finished...) job.returncode is always None, therefore I can't run if statement and remove jobs from the processing job list.
I know it can be done with process.communicate() or process.wait() but I don't want to block the thread that launches the processes. Is there any other way to do it, maybe using a ProcessPoolExecutor? The queue can be hit by processes at any time and I need to be able to handle them.
Thank you all for your time and support :)
from queue import Queue
import subprocess
from threading import Thread
from time import sleep
class JobQueueManager(Queue):
def __init__(self, maxsize: int):
super().__init__(maxsize)
self.processing_jobs = []
self.process = None
self.jobs_launcher=Thread(target=self._worker_job)
self.processing_jobs_checker=Thread(target=self._check_jobs_status)
self.jobs_launcher.start()
self.processing_jobs_checker.start()
def _worker_job(self):
while True:
# Run at max 3 jobs concurrently
if self.not_empty and len(self.processing_jobs) < 3:
# Get job from queue
job = self.get()
# Execute a task without blocking the thread
self.process = subprocess.Popen(job)
self.processing_jobs.append(self.process)
# util if queue.join() is used to block the queue
self.task_done()
else:
print("Waiting 4s for jobs")
sleep(4)
def _check_jobs_status(self):
while True:
# Check if jobs are finished
for job in self.processing_jobs:
# Sucessfully completed
if job.returncode == 0:
self.processing_jobs.remove(job)
# Wait 4 seconds and repeat
sleep(4)
def main():
q = JobQueueManager(100)
task = ["stress", "--cpu", "1", "--timeout", "20"]
for i in range(10): #put 10 tasks in the queue
q.put(task)
q.join() #block until all tasks are done
if __name__ == "__main__":
main()
I answer myself, I have come up with a working solution. The JobExecutor class handles in a custom way the Pool of processes. The watch_completed_tasks function tries to watch and handle the output of the tasks when they are done. This way everything is done with only two threads and the main thread is not blocked when submitting processes.
import subprocess
from threading import Timer
from concurrent.futures import ProcessPoolExecutor, as_completed
import logging
def launch_job(job):
process = subprocess.Popen(job, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"launching {process.pid}")
return [process.pid, process.stdout.read(), process.stderr.read()]
class JobExecutor(ProcessPoolExecutor):
def __init__(self, max_workers: int):
super().__init__(max_workers)
self.futures = []
self.watch_completed_tasks()
def submit(self, command):
future = super().submit(launch_job, command)
self.futures.append(future)
return future
def watch_completed_tasks(self):
# Manage tasks completion
for completed_task in as_completed(self.futures):
print(f"FINISHED task with PID {completed_task.result()[0]}")
self.futures.remove(completed_task)
# call this function evevery 5 seconds
timer_thread = Timer(5.0, self.watch_completed_tasks)
timer_thread.setName("TasksWatcher")
timer_thread.start()
def main():
executor = JobExecutor(max_workers=5)
for i in range(10):
task = ["stress",
"--cpu", "1",
"--timeout", str(i+5)]
executor.submit(task)
I am new to queue & threads kindly help with the below code , here I am trying to execute the function hd , I need to run the function multiple times but only after a single run has been completed
import queue
import threading
import time
fifo_queue = queue.Queue()
def hd():
print("hi")
time.sleep(1)
print("done")
for i in range(3):
cc = threading.Thread(target=hd)
fifo_queue.put(cc)
cc.start()
Current Output
hi
hi
hi
donedonedone
Expected Output
hi
done
hi
done
hi
done
You can use a Semaphore for your purposes
A semaphore manages an internal counter which is decremented by each acquire() call and incremented by each release() call. The counter can never go below zero; when acquire() finds that it is zero, it blocks, waiting until some other thread calls release().
A default value of Semaphore is 1,
class threading.Semaphore(value=1)
so only one thread would be active at once:
import queue
import threading
import time
fifo_queue = queue.Queue()
semaphore = threading.Semaphore()
def hd():
with semaphore:
print("hi")
time.sleep(1)
print("done")
for i in range(3):
cc = threading.Thread(target=hd)
fifo_queue.put(cc)
cc.start()
hi
done
hi
done
hi
done
As #user2357112supportsMonica mentioned in comments RLock would be more safe option
class threading.RLock
This class implements reentrant lock objects. A reentrant lock must be released by the thread that acquired it. Once a thread has acquired a reentrant lock, the same thread may acquire it again without blocking; the thread must release it once for each time it has acquired it.
import queue
import threading
import time
fifo_queue = queue.Queue()
lock = threading.RLock()
def hd():
with lock:
print("hi")
time.sleep(1)
print("done")
for i in range(3):
cc = threading.Thread(target=hd)
fifo_queue.put(cc)
cc.start()
please put the print("down") before sleep.
it will work fine.
Reason:
your program will do this:
thread1:
print
sleep
print
but while the thread is sleeping, other threads will be working and printing their first command.
in my way the thread will write the first, write the second and then go to sleep and wait for other threads to show up.
I have 2 processes. I would like write.py to write a message to read.py through a Linux pipe.
### write.py
import subprocess
import asyncio
from threading import Thread
# !-- Offending code here. --!
# loop = asyncio.new_event_loop()
# def side_thread(loop):
# asyncio.set_event_loop(loop)
# loop.run_forever()
# thread = Thread(target=side_thread, args=(loop,))
# thread.start()
notify_proc = subprocess.Popen("python read.py".split(), stdin=subprocess.PIPE)
notify_proc.stdin.write("hello\n".encode("utf-8"))
### read.py
import sys
for line in sys.stdin:
print("Got something!")
print(line)
As given, when I run python write.py, I get the expected output:
Got something!
hello
But when I uncomment the offending code, I don't get any output. Why?
Python 3.9.7
Linux 5.4.72-microsoft-standard-WSL2 x86_64
Your buffer won't get sent over until the buffer is flushed or the process ends, and the process will not end as long as a non-daemon thread is running. In the short term, you can add notify_proc.stdin.flush(), but your mixing of multiprocessing, threading and asyncio is going to lead to tears. What on earth are you trying to do here?
The following example program uses QThread instances to run jobs from a queue.
from queue import Queue
from sys import argv
from threading import Lock
from time import sleep
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication
class Worker(QObject):
finished = pyqtSignal()
def __init__(self, number):
super().__init__()
self.number = number
#pyqtSlot()
def work(self):
while True:
job = queue.get()
if job is None:
self.finished.emit()
return
with lock:
print('worker={} job={}'.format(self.number, job))
sleep(1)
app = QApplication(argv)
lock = Lock()
queue = Queue()
threads = []
nthreads = 4
for ithread in range(nthreads):
thread = QThread()
worker = Worker(ithread + 1)
worker.moveToThread(thread)
thread.started.connect(worker.work)
worker.finished.connect(thread.quit)
thread.start()
threads += [thread]
#-----------
sleep(1e-10)
#-----------
for ijob in range(10):
queue.put(ijob + 1)
for _ in range(nthreads):
queue.put(None)
for thread in threads:
thread.wait()
With the sleep call (see marked line), all threads are running as expected. Output:
worker=1 job=1
worker=2 job=2
worker=3 job=3
worker=4 job=4
[…]
Without the call, an arbitrary number of threads is running. Output:
worker=4 job=1
worker=4 job=2
worker=4 job=3
worker=4 job=4
[…]
I have tested this with PyQt 5 in Python 3.6.2, other versions, a variable sleep duration, different orders of statements, and a running event loop.
Why does the sleep call change the number of running threads?
The first three worker objects are prone to garbage-collection, whilst the fourth one is left as a global variable. The sleep allows just enough time for each thread to call the work method of its associated worker, and this will keep them all alive (since the methods run blocking while-loops). Without the sleep, the first three workers will be immediately garbage-collected, leaving only the fourth to process the queue. If the workers are kept in a list (in the same way as the threads), you should see all of them being utilised (with or without the sleep).
To prove that this is what is going on, you can add this to the Worker class:
class Worker(QObject)
...
def __del__(self):
print('deleted:', self.number)