Start Popen Low Priority Process

I am looking for a way to efficiently run several low priority processes on Windows. I tried:

def run(command): # command['Program.exe args1 args2','output_file'] try : p = subprocess.Popen(command[0] , stdout = command[1]) psutil.Process(p.pid).nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) p.wait() except Exception as e: print(e) raise SystemExit 

The problem is this: low priority is not set immediately. I freeze at first. When I look closer in the process window, I see the priority of the application, starting with high_priority and switching to low_priority.

I would like to start at a low priority right away or find another way to block CPU usage (now 100%).

Then I use the run command inside the multiprocessor pool (a few seconds for each start).

 def safe_run(args): """Call run(), catch exceptions.""" try: run(args) except Exception as e: print(args[0]) print(e) def parallel(commands,nb_proc): # populate files # start processes if len(commands) < 10: nb_proc = 1 print('Use of {} cpus\n'.format(nb_proc)) pool = mp.Pool(nb_proc) pool.map(safe_run, commands, chunksize=1) 

UPDATE

Test.exe is the fortran code:

  integer function NumArguments() integer :: IARGC NumArguments = IARGC() end function subroutine GetArgument(Idx,Argument) integer, intent(in) :: Idx character(LEN=*), intent(out) :: Argument call GETARG(Idx,Argument) end subroutine program Console implicit none integer, parameter :: INTEG = SELECTED_INT_KIND(9) integer(INTEG), parameter :: MAX_STRING_LEN = 1024_INTEG character(LEN=MAX_STRING_LEN) :: FileName integer(INTEG) :: i call GetArgument(1,FileName) ! Body of Console !print *, 'Hello World' !print *, FileName call sleep(5) open(unit=1, file=FileName,status='new') Do i=1,1000,1 write(1,*) i Enddo close(unit=1) end program Console 

Full code:

 # -*- coding: utf-8 -*- """ """ ############################################################################### ############################################################################### # # IMPORT & INIT # ############################################################################### ############################################################################### import psutil import subprocess import time import multiprocessing.dummy as mp import os TEST_EXE = "Console.exe" nb_proc = 4 ############################################################################### ############################################################################### # # FUNCTION # ############################################################################### ############################################################################### def run(command): try : print(command[0]) psutil.Process().nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) # lower priority p = subprocess.Popen(command[0] , stdout = command[1]) psutil.Process().nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) # lower priority p.wait() except: print('Point {} fail'.format(point)) raise SystemExit def safe_run(args): """Call run(), catch exceptions.""" try: run(args) except Exception as e: print('{} error'.format(args[0])) def parallel(commands,nb_proc): print('Use of {} cpus\n'.format(nb_proc)) pool = mp.Pool(nb_proc) pool.map(safe_run, commands, chunksize=1) ############################################################################### ############################################################################### # # MAIN SCRIPT # ############################################################################### ############################################################################### current_dir = os.path.abspath('') print('\nCurrent directory {}'.format(current_dir)) t1 = time.time() logfiles = list() commands = list() logfiles_obj = list() for step in range(100): logfile = open(os.path.join(current_dir,'logfile_'+ str(step) + '.out'), 'w') args = TEST_EXE + ' ' + os.path.join(current_dir,'output_'+str(step) + '.txt') temp = (args,logfile) commands.append(temp) # run in parallel print("Calculation running ...\n") parallel(commands,nb_proc) for log in logfiles_obj: log.close() # time for running all the point and complete t2 = time.time() print ("\n ########## Overall time : %5.2f secondes ##########" %(t2 - t1)) print("\n ########## Correct ending ##########") 
+8
source share
3 answers

The usual way on a Posix system would be to use the preexec_fn parameter in subprocess.Popen to call the function before running the command (see preexec_fn in this answer for details). Unfortunately, this must happen between the system calls fork and exec and Windows does not create processes this way.

On Windows, the system call (WinAPI) used to create CreateProcess is CreateProcess . The MSDN page says:

 BOOL WINAPI CreateProcess( ... _In_ DWORD dwCreationFlags, ... ); 


dwCreationFlags [in]
Flags controlling the priority class and process creation ... This parameter also controls the new process priority class, which is used to prioritize scheduling process flows.

Unfortunately, the Python interface does not provide for setting a child priority, because it is explicitly said:

The creation flag, if specified, can be CREATE_NEW_CONSOLE or REATE_NEW_PROCESS_GROUP. (Windows only)

But the documentation for dwCreationFlags on MSDN also states:

... If none of the flags of the priority class is specified, the default priority class is NORMAL_PRIORITY_CLASS, if the priority class of the creation process is not IDLE_PRIORITY_CLASS or BELOW_NORMAL_PRIORITY_CLASS . In this case, the child process receives the default priority class of the calling process .

This means that the priority can simply be inherited, since the way to control the child priority in Python for Windows is to set the priority before starting the subprocess and reset it immediately after:

 def run(command): # command['Program.exe args1 args2','output_file'] try : psutil.Process().nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) # lower priority p = subprocess.Popen(command[0] , stdout = command[1]) # start child at low priority psutil.Process().nice(psutil.NORMAL_PRIORITY_CLASS) # reset current priority p.wait() except Exception as e: print(e) raise SystemExit 

The remainder of this answer will relate to a Posix system such as Linux or Unix.

The preexec_fn parameter in Popen is what you need. This allows you to call the called object (for example, a function) between the creation of the child process and the execution of the command. You could do:

 def set_low_pri(): psutil.Process().nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) 

then use it to have a low priority baby:

 def run(command): # command['Program.exe args1 args2','output_file'] try : p = subprocess.Popen(command[0] , stdout = command[1], preexec_fn=set_low_pri) p.wait() except Exception as e: print(e) raise SystemExit 

In this way, Python ensures that low priority is set before executing your command.


Link: The documentation for the subprocess module reads:

17.5.1.2. Popen constructor
...

 class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None) 

...
If preexec_fn is set for the called object, this object will be called in the child process immediately before the execution of the child process. (POSIX only)


But the above method is not thread safe! If two threads work simultaneously, we can get into the following race condition:

  • thread A lowers priority
  • thread A starts its child (low priority)
  • thread B lowers priority (no operation)
  • thread A resets normal priority
  • thread B starts its descendant with normal priority
  • thread B resets normal priority (no operation)

The problem is that multiprocessing.dummy is a wrapper for threading . The Python standard library documentation (3.6) is written in 17.2.2.13. Multiprocessing.dummy module

multiprocessing.dummy copies the multiprocessing API, but is nothing more than a wrapper for a thread module.

Once the problem is discovered, the fix becomes trivial: just use Lock to protect the critical section:

 lock = mp.Lock() def run(command): try : print(command[0]) lock.acquire() psutil.Process().nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) # lower priority p = subprocess.Popen(command[0] , stdout = command[1]) psutil.Process().nice(psutil.NORMAL_PRIORITY_CLASS) # normal priority lock.release() p.wait() except: print('Point {} fail'.format(point)) raise SystemExit 
+6
source

surprised that no one proposed it, but just because the subprocess module does not reveal the required constants does not mean that we cannot pass them to the module to set priority:

 import subprocess ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000 HIGH_PRIORITY_CLASS = 0x00000080 IDLE_PRIORITY_CLASS = 0x00000040 NORMAL_PRIORITY_CLASS = 0x00000020 REALTIME_PRIORITY_CLASS = 0x00000100 p = subprocess.Popen(["notepad.exe"], creationflags=BELOW_NORMAL_PRIORITY_CLASS) p.wait() 

this sets the creation flags correctly and starts the process with the priority set to expose it correctly, and _winapi and subprocessing modules must be fixed (to make part of the module constants, not a sketch)

+5
source

In Python 3.7, this is available for Windows (but not for Posix):

 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.BELOW_NORMAL_PRIORITY_CLASS subprocess.Popen(command, startupinfo=startupinfo) 

for Linux:

 subprocess.Popen(command, preexec_fn=lambda : os.nice(10)) 
0
source

Source: https://habr.com/ru/post/1272774/


All Articles