This post does not grant a universal one-size-fits-all, but given the above posted pieces of information, this approach does nothing but the requested set of features:
a ) processing must perform a fileIO output ( the compressed file )
b ) processing must report a result / status to the central code
c ) processing does not exchange any additional information or synchronisation
Let's mock up such processing:
>>> def aFunToProcessGivenSTRING( aString2PROCESS = "<aStringNotPassedOnCALL>" ):
... time.sleep( 5 ) # emulate doing compression "efforts"
... # # ( yes, w/o loading a CPU-core )
... return ( "[pid:{0:}]->( {1:} )".format( os.getpid(),
... aString2PROCESS
... )
... )
Let's mock up also a list of "Tasks" for such aFunToProcessGivenSTRING()
:
aListOfTaskSTRINGs = [ '6554,a,0.109375,2;',
'6554,c,0.099609375,2;',
'6557,g,0.109375,2;',
'6555,b,0.099609375,2;',
'6555,f,0.109375,2;',
'6556,q,0.109376,2;',
'6558,r,0.109375,2;',
'6559,s,0.109376,2;',
'6553,t,0.109375,2;',
'6553,d,0.109375,2;',
'Some more Meat for Dr.Jackson',
'The last but not the least...'
]
The most lightweight approach, using an async version of the apply()
, a multiprocessing.Pool()
builtin, for data-driven parallelism goes as light as this:
import multiprocessing, os, time
aPool = multiprocessing.Pool( 2 ) # Here, allow 2-Process-streams to take place
print( aPool.map( aFunToProcessGivenSTRING, aListOfTaskSTRINGs ) )
['[pid:12387]->( 6554,a,0.109375,2; )', '[pid:12388]->( 6554,c,0.099609375,2; )', '[pid:12387]->( 6557,g,0.109375,2; )', '[pid:12388]->( 6555,b,0.099609375,2; )', '[pid:12387]->( 6555,f,0.109375,2; )', '[pid:12388]->( 6556,d,0.109375,2; )', ..., '[pid:12387]->( Some more Meat for Dr.Jackson )'
'[pid:12388]->( The last but not the least... )'
]
Check the 2 Process
instances via reported "remote"-os.getpid()
-#s.
Given no other interactions are intended, this ought serve best for your file-compression project.
How "big" the .Pool()
-instance ought get?
Well, this requires a bit deeper insight into resources-mapping. Given your processing interacts with fileIO, there might be some bottlenecks on hardware level ( disk hardware uses a pure-[SERIAL]
-arrangement of atomic fileIO chunks, located on physical 4D-locations: PLATE:CYL:HEAD:SEC and as such a disk cannot read from more such 4D-locations "at once" ( sure, controller elevator-optimiser helps a lot, caches do either ), yet there is a performance limit no further [CONCURRENT]
-scheduling of tasks will ever overcome ).
Next typical ( and negative ) surprise comes when a multiprocessing.Pool()
-mapped task already wants to use some process-mapped parallelism ( and some module functions do ). Spawning a .Pool()
instance, covering "just"-enough the available CPU cores is always better, than "over-sized" .Pool()
, which then coughs in waiting for tasks being actually CPU-assigned by the O/S scheduling, as there are more processes than free CPU-cores. In case the .Pool()
-mapped function spawn their own Process
-instances, the "suffocation" gets multiple-times worse.
Similar colliding resource is memory. In case a process-to-run demands some amount of memory, the wise capacity planning ought take due care of this, as no one would enjoy the process-flow to get O/S into almost endless swapping.
So, a careful resources-planning and "just-enough" resources-mapping is a way to achieving the top performance possible. "Suffocated" process-flow is always a bad sign of such performance-motivated engineering practice. Never underestimate a systematic approach to testing and benchmarking on scales, that are close to your use-case - if interested in more details about the actual costs of instantiations, under different python tools, this read may give you directions to follow.