"The greatest power of the mass media is the power to ignore. The worst thing about this power is that you may not even know you're using it." --Sam Smith
No sense wasting your time with a boring intro, so let's just get right to it.
Assumptions
I am going to assume the following:
* That you are at least somewhat familiar with perl
* That you know how to edit text files
* That you know how to install modules
If any of these are not true, I can't guarantee you will get the full benefit of this article.
Also, I am going to make the assumption that you will forgive the sloppy code formatting. Non-breaking spaces are a bitch to type out.
The Problems
You want your program to use flexible command-line arguments
Sure, perl has $ARGC and $ARGV[] which work just fine for a lot of applications. One major problem, however, is that you have to type in the arguments in a specific order. This can be a minor annoyance at best, and a catastrophic error at worst. In addition, there isn't a way to specify boolean switches (such as -d for debug mode).
Your program needs to do many things at once
There really isn't a great way to do this by default. I suppose you could write some pretty barbaric sleep()-based timing system to manage all the tasks, but that is neither elegant nor efficient. Imagine writing a GUI application where you couldn't refresh an animated object AND click a button at the same time.
The Solutions
Fortunately for you, a bunch of really smart people have already solved these problems!
getopts()
The first issue is resolved by the use of getopts(). This function is provided by the Getopt::Std module. There is a more advanced version provided by Getopt::Long, but I will not cover that here. getopts() allows you to run something like
CODE :
user@home$ perl someprog -d -n5 -u goatboy
where "-d" means to enable debugging messages, "-n5" might mean you want to choose 5 of something, and "-u goatboy" is the user. Notice how you can have nothing after the switch, or a value attached to the switch, or a value after the switch. And this is just the basic module!
Threading
One thing you'll eventually want to do is two things at once. Walk and talk. Chew and swallow. Sleep and breathe. Or in programming, update a timer and perform calculations based on the current time. This is made possible by threading. Simply put, threading lets you specify two (or more) tasks to be run at the same time, instead of waiting for one to finish before the other can start. You don't really need to know a lot more than that right now.
Threading is provided by the "threads" module.
The Code
The following program works like this:
A specified number of threads (specified by -t) is created, and each begins to guess numbers from 0 to 100. There is a random delay in between each guess (0 to 5 seconds) so the output is both more readable and easy to follow. If one thread gets above the target number (specified by -n) then the game is over and that thread is the winner!
I will now go over the code piece by piece:
CODE :
#!/usr/bin/perl
use threads;
use threads::shared;
use Getopt::Std;
use Time::HiRes qw(usleep);
Pretty standard stuff. The only modules I haven't mentioned yet are threads::shared, but I'll explain that shortly, and Time::HiRes. The latter allows us to use fine-grained usleep() calls that can use fractions of a second as opposed to rounding off like sleep(). Bear in mind that these modules are case-sensitive.
CODE :
# Create vars for options
getopts('n:t:');
# -n specifies the target [n]umber to win
# -t specifies the number of [t]hreads to create
print "You have chosen to use $opt_t threads.\n";
print "The goal is to get $opt_n or higher.\n\n";
This section does just as it says in the comment: it creates variables for each command-line argument. They are created in the form of $opt_* where * is the letter you specify. Notice the format:
CODE :
getopts('n:t:');
The colon means that the preceding character will accept a value. To for example, you could run it as "perl myprog -t 5 -n 95" to use 5 threads and set the winning number at 95. If a switch has no colon after it, then it is a boolean switch. I do not use any of those in this program.
I print out some simple informational messages to demonstrate the usage.
CODE :
# This is a shared variable that is set to 1 when all threads should end
my $stop :shared = 0;
Threads by default do not share any information between them. In order to communicate, they must typically pass messages. That is a bit out of scope for this simple intro, so I used a simpler method. The threads::shared module allows you to define a variable as shared between all threads. This means that any thread can, at any time, read or modify that variable. This can cause some problems such as race conditions in more advanced programs, but there's no risk of that here.
This shared variable is used as a simple "signal" that all threads should stop. If it is 0 (initially it is) then threads should continue executing. If it is 1, they should stop immediately. It is set to 1 by the thread that guesses a number higher than -n. You will see this later.
CODE :
# Create [t] threads
my @threads;
for ( my $i=1; $i<=$opt_t; $i++ ) {
push(@threads,$i);
}
foreach(@threads) {
$_ = threads->create(\&guess);
}
This is basically a cryptic-looking piece of code that creates as many threads as you have specified with -t. If you know some perl, it shouldn't be too hard to understand, but basically it creates an array with 1 to "t" variables using a for loop, then creates a thread for each variable. You might be wondering why I used the array and a for loop instead of just the loop, but I'll explain this later.
The important takeaway here is how threads are actually made. You use the "threads->create()" method to create a thread, and you pass into it a reference to a function. In this case, the function name is "guess", which is our next chunk of code.
CODE :
# Function that actually defines the behavior of the threads
sub guess
{
# Get process id
my $id = threads->tid();
while(1) {
# If another thread has already won, exit
if ( $stop ) {
threads->exit();
}
# Generate your guess and wait time
my $rand = int(rand(100));
my $wait = (rand(5)*1000000);
print "Thread #$id produced: $rand\n";
# If you have won, set the stop flag to 1 and exit
if ($rand >= $opt_t) {
print "$id wins!\n\n";
$stop = 1;
threads->exit();
}
else {
usleep($wait);
}
}
}
It's pretty big, but I'll take you step-by-step again.
First we create a variable called $id and assign it the value returned by the threads->tid() method. tid stands for Thread ID, and is how you tell one thread from another. Also useful for debugging.
Next we enter into a while loop. Each repetition is a cycle of:
* See if another thread has won
* Make your guess
* Wait
We check to see if another thread has won already. This is done by checking the value of the shared $stop variable I mentioned earlier. If it's set to 1 (AKA "true") then another thread has won already and we can exit. This is done, appropriately enough, by calling the threads->exit() method.
Assuming nobody has won, we next generate two values. The first is our guess, a random whole number from 0 to 100, and the second is our waiting period, a random number from 0 to 5, allowing decimals to make things interesting. We use the usleep() command from the Time::HiRes module to allow us to have fractions of a second in our pauses. Without this, it would be like watching the second hand of a clock; every thread would pick a number in the same interval.
We output some text saying which process guessed which number.
We next check to see if our guess is at or above the value specified by -n. If so, we declare the thread's victory, set the shared $stop variable to 1 (so that other threads don't keep on going) and we exit the thread. If we have not won, we wait a few seconds and the cycle continues.
CODE :
# Make sure threads all get the love they deserve
# This is needed so that the main program doesn't die before
# the game can finish, instead waiting for each to exit
foreach(@threads) {
$_->join();
}
This last part had me confused for the longest time. Here's why:
When you run a normal program (no threads) it's fairly straight-forward: When it's done, it's done. It either reaches the end of the script of hits a function like exit() or return(). Threaded programs are a tad bit different. Let's say you have three threads, in addition to the main program. If the main program finishes while the threads are still executing, they will be killed on the spot! This is certainly not a desired behavior in most cases, so we need a way to keep the main program running.
We could use a while loop that checks if the threads are done, but that's cludgy and eats up a ton of clock cycles (seriously, try running a while(1){;} some time). A better way would be to have the main program simply wait until the threads finish before itself finishing. This is where threads->join() comes in.
Basically, threads can be one of two types: Detached or Joined. A Detached thread executes on its own with little interaction from the main program. It returns no values, and the main program doesn't care if it finishes or not. A Joined thread will return a value (even if just a success/failure indicator), and the main program will let it finish completely.
So basically what this last section does is tell the main program to chill out and wait until each of the threads is finished. It does this via a threads->join() call to each thread in the array I used earlier. Told you I had a good reason for using an array!
The Results
Wow. That was so little code but there's a lot to learn from it. I know, I know. You wanna see it in action. Here's a random run:
CODE :
goatboy@goatserv:~$ ./threadopt.pl -t 10 -n 95
You have chosen to use 10 threads.
The goal is to get 95 or higher.
There's so much that can be done with just these two simple concepts. Granted, I focused a lot more on threading than on getopts(), but I think that speaks a bit towards the usefulness of each (or perhaps the complexity).
This script can be improved in many ways, such as using proper IPC (Inter-Process Communication) to signal a win, allowing for more options (min and max values), and putting more logic into the wait times (perhaps if you get the lowest score in a given time period you have to wait longer).
I've already spoken too much, so I'll just leave with the hope that this helped someone and a big thank you to... idk, people and stuff.
Cast your vote on this article 10 - Highest, 1 - Lowest
Comments: Published: 2 comments.
HackThisSite is is the collective work of the HackThisSite staff, licensed under a CC BY-NC license.
We ask that you inform us upon sharing or distributing.
Page Generated: Fri, 24 May 2013 04:13:13 +0000 Web Node: www0 | Page Gen: 0.144s | DB: 11q Current Code Revision: Thu Dec 6 19:06:02 UTC 2012