Case Else:

/*** Code’s last stand ***/

Case Else: Killer Whales hunting off the Haida Gwaii (or Queen Charlotte Islands)

Multithreading in Batch Script, Part 1: An Example

May 22nd, 2008 · No Comments

Multithreaded applications have the potential to run much faster than single-threaded applications, given the right circumstances. With batch scripts, we often write routines that spend a lot of their time waiting for other things to finish; downloading sets of files, for example, or pinging a range of servers to see if they are alive.Various Agent Smiths Blocking functions (ie, where the scripts stalls until they are done) that spend their time waiting for other things are the big beneficiaries of multithreading. With some care, we can get the same benefits by ‘multithreading’ our batch scripts.

What makes multithreading worthwhile is lag and the fact that “all computers wait at the same speed”. Without some sort of wait, threading can actually slow a system down due to overhead. Think of it like this: the pony express moved mail at the maximum speed a horse could travel. If they had implemented it with two or more horses, running parallel, the mail wouldn’t have gotten there any faster. two horses (ignoring load, so carriages don’t enter into this) don’t run faster than one horse. On the other hand, if running parallel horses doesn’t increase your speed, but you add the overhead of having to split the mailbags (ie, take the time to count or weigh the mail, so that the horses carry the same amount) then you are running at a net loss.

The pony express, however, was a system that was running at near maximum efficiency from our point of view. Adding more horses into the mix could increase complexity and overhead, but not speed up the system. It’s a different story for pizza delivery boys, or jugglers. Jugglers spend most of their time waiting for the ball to drop–literally. They throw a ball into the air, and it flies up, stops, falls back down. It isn’t until the ball comes back down that the juggler has any more input into the process. A juggler can therefore achieve higher efficiency by launching multiple asynchonous balls. They’re not parallel, per se, but they are fired off one after another, and function on their own. In this case, the balls can slightly change the ‘running’ of each other. To increase the numbers of balls in the air, it becomes necessary to throw them higher, requiring that their ‘runtime’ be slightly longer. In general, though, you can keep adding balls until you are running your juggler at maximum speed before you have to lengthen the ‘run’ of the balls. Once your juggler is maxed out, it will affect the speed of the system, but you’ve still drastically increased the amount of stuff that’s going on by utilizing the juggler’s idle time.

Say a busy pizza company guarantees delivery within x minutes. In this case, using one delivery boy could get very expensive–any delivery x/2 minutes away takes up the delivery boy’s whole transit time (x/2 minutes each way). Any order placed while the delivery boy is out will be delayed while he is out, and could easily be delayed beyond the time limit. The company could try to maximize their efficiency by organizing deliveries together on a trip, and hiring the fastest possible driver to minimize the transit times–but the customer is a wildcard. Nobody knows how long it’s going to take a customer to check their order, look for their checkbook, find out checks aren’t accepted, look for cash, and work out a tip–and all pizza boys wait at the same speed. What they need to do is hire several delivery boys, have each one take one existing order and leave (in parallel) and as soon as they are back, take the next order and run (asynchronously). In the pizza business, they probably want to grab the pizza that needs to be delivered furthest, but they could always settle on the first-come, first-served model.

Multithreading requires a process to run, and a control system to keep it from running amok. Below we have the control system for a simple multithread emulator. It takes a pool of necessary tasks and keeps a list of available threads that it can use to process those tasks. Our model is most like the pizza company. We have arbitrary length processes, and are going to fire off as many as we can at once, and as each finishes, it grabs the next and runs. We don’t prioritize the machines, so they’ll just be first-come, first-serve.

Our ‘thread’ process will need do several things, in order to function as an efficient system.

  1. it will quarter-ping the target, to make sure it is online
  2. if the quarter ping succeeds, excellent, but on failure the thread will perform a full ping, to account for network connectivity issues
  3. if the machine is found online, the thread will map a drive to that machine’s default admin shares
  4. the tread will perform an arbitrary process against the target

The first three actions all require waiting for the network or the target machine to respond. While waiting, the process will use around 0% of the CPU. This makes it a candidate for threading. If processing a single target machine took 100% of the CPU, then threading would take more resources than it returned. In our case, we can take advantage of unused cycles.

  • We’re going to emulate the task pool with a list of machines that need the same task run on each of them.
  • We’ll also emulate the available threads with a list of available network drives. In a full application, we’ll be mapping a drive to each of the PCs, so using the available drives gives us both a control (we only run as many threads as we have drives, so we can change the number of threads we wish to run) and a mechanism to identify what is connected where. This has the side-effect of making this file specific to a computer. To make it universal, we would want to have it generate it’s own list based on what drives are not mapped in the system.

@ECHO OFF
(set drivestring=J K L)&(set childtemplate=thread.bat)&(set inputfile=computers.csv)
Set outputfile=.\log.csv
Echo.number,machine,ip> %outputfile%
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Set variables above.
:: Make sure drivestrings reflect executing machine's available drives.
:: If common outputfile is used, make sure it matches in %childtemplate%
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
set /P USERID=Enter your domain\userid:
set /P PASSWORD=Enter your password:
cls
 
@ECHO OFF
MD logs
set strlen=0
for %%A in (%drivestring%) do SET /A strlen+=1
 
SET count=0
 
FOR /F "delims=" %%A IN (%inputfile%) DO (
 call SET /A count+=1
 echo.
 call set comline=%%A
 call :processmachine %%A
)
pause
goto :EOF
 
:processmachine
  echo Processing #%count%: %comline%
  set newletter=
  @for %%D in (%drivestring%) do (
   @if NOT exist %%D.bat (
    @if NOT exist %%D:\nul (call set newletter=%%D)
    @if exist %%D:\nul Echo %%D: already connected. Skipping.
   )
  )
  if NOT DEFINED newletter (
   nul
   goto processmachine
  ) ELSE (
   @echo new letter = '%newletter%'
   type %childtemplate% > %newletter%.bat
   START "%count% -- %newletter%.bat" %comspec% /c newletter%.bat %count% %newletter% %comline%
 )

download master.bat
The master takes the list of available thread slots, and for each one, it makes a copy of the thread template named x.bat (where x=slot name), and launches that bat file. When it runs out of slots, it sleeps briefly, and looks through the list, to see if any of the bat files are missing–thereby using the bat file itself as a crude lock. As long as the file is running, and the file is there, that process ‘owns’ that slot (and the corresponding drive mapping). The last thing a thread needs to do before it exits, is to delete itself.

This is weak as threaded systems go; it relies on the individual threads to clean up after themselves. If a thread batch dies, it leaves behind an orphaned lock file, and that thread doesn’t get reused–resulting in a resource leak. A better alternative would be to check the running process list for each thread-bat rather than relying on the existence of the lock.

All of this should fall well short of the mark for qualifying as a true ‘multithreaded’ system; for the purpose of this discussion, I’m using the term ‘multithreading’ loosely, vaguely, and expansively. I believe each command prompt executes in its’ own processor thread (although that would be worth looking into); but that shouldn’t be enough to classify it as true multithreading. “Asynchronously executing multiple simultaneous batch processes” is probably a better description. This is, however, sufficient for quickly assembling network compliance scanners, application fixes, and tools of that nature.

The master runs against a list of PCs; for each machine, launching a thread, and passing it some pertinent information. To that end, we need a list. The master.bat is set up to read from a CSV file named computers.csv. For our purposes, all it needs to be is a list of PC Names, one per row:

PCName1
PCName2
PCName3
PCName4
PCName5

download computers.csv
Below, you will find a simple thread template. This one is really just an illustration, so it’s short; in part two, we will look at a significantly more intricate version.

@echo off
(set ip=%3)
(set pcname=%3)
(set drive=%2)
(set num=00000%1)
 
Title Thread %~n0 handling %PCNAME%
 
<nul (set/p z=Running)
echo.
 
    for /l %%A in (%1,1,10) do (
        <nul (set/p z=%%A )
        >nul ping 127.0.0.1 -n 2
    )
echo.
 
Del %0

download thread.bat
This bat simply puts up a progress bar, then exits. Each bat runs for one second less than the previous one, beginning with 10 seconds. You can run the bat, if you like–it makes no changes on the machine–but realize that the last thing it does is delete itself. (Nothing will get you in the habit of backing up your work faster than working on self-deleting scripts.)

If you copy these three files down to a common directory and run them, it should all work. They’re set up to run five machines on three available threads, so you can get a good idea of how it works. When run, the master.bat will prompt for a username/password combination. This isn’t actually used in this iteration (it’s in there for Multithreading in Batch Script, Part 2: Running Code). You can just enter through the prompts (Multithreading in Batch Script, Part 2: Running Code is written so that if you enter through the prompts, it will use your current credentials.)

Tags: Batch Scripts

0 responses so far ↓

  • There are no comments yet...Kick things off by filling out the form below.

Leave a Comment