mcogan1966 has asked for the wisdom of the Perl Monks concerning the following question:

This is a bit of a logic issue, and I can't quite see my way through it at the moment.

I have a main program that calls 7 other programs in parallel (this logic has be resolved). I am passing to each of these programs, by reference, a flag variable, called $lock. In each of the sub-programs, they are checking the value of $lock. The sub-program will only act if $lock is false, but will wait until the condition is set to false.

My issue is this: If I start with $lock set to false, then my test while ($lock) { never activiates. But, if I use while(!($lock)) {, then I never really properly do the check, and the section of code will skip.

flock() is not an option for this, as this program will need to be able to be run by multiple users at the same time. flock() would be working on the same file for every user, so it would be contending with itself.

So, how to I keep all the sub-programs from acting all at once, and avoid none of them ever acting at all because they are all waiting indefinately for something to happen?

Addnedum: I have figured out the code to work this through. It involves testing to see if the lock is on or off, then waiting if the lock is on, and just going ahead if the lock is off. My issue now seems to be that if I pass the reference to $lock, the value of that reference does not seem to go with it. The sub-programs are called (forked) off the main program, with the reference to $lock passed as an argument.

Replies are listed 'Best First'.
Re: Logical Conundrum
by Abigail-II (Bishop) on Dec 01, 2003 at 15:05 UTC
    There are many solutions to that problem. The simplest is that if the processes aren't supposed to run at once, don't start them in parallel! Start one, let it finish, start the next, let it finish, etc.

    Otherwise, I'd use a lockfile, or a locking mechanism in a database. You say flock() doesn't work, but I fail to understand why. Since only one process at a time can have an exclusive lock at one time, this seems like an ideal mechanism.

    Abigail

      Actually, they DO have to run in parallel, or else the results will take several minutes to generate, rather than just 10-15 seconds. This is crucial, as no one wants to wait long for a multi-headed search engine to print out results.

      As for flock() not working, it's not that it doesn't work, it's that I had the same logic issue as with a flag. But I think I've found the way around that. I'm going to write it up, test it, then post it back here to this thread once I've got it worked out.

        As for flock() not working, it's not that it doesn't work, it's that I had the same logic issue as with a flag.
        I don't understand your logic issues. That is, if you want only one process to act at once (as you said in your original post). But now that you say that you do want them to run in parallel, I'm confused. What do you really want? One-by-one, or in parallel? You can't have both.

        Abigail

•Re: Logical Conundrum
by merlyn (Sage) on Dec 01, 2003 at 16:06 UTC
    Neither "while $lock" nor "while ! $lock" are "atomic test and set" operations. You need to gate your critical (one-user-at-a-time) resource with some operation that both notices if a resource is available, and grabs that resource if so. Also, making such a request should block the process at the kernel level.

    That's why flock is nice for that, if you have that. I have an example of that in my famous HIGHLANDER article.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      I've worked this out to something of the following design:
      if (locked) { while (locked) { if (unlocked) { lock print unlock break; } } } else { lock print unlock }
      The only thing I need to make this work is a flock() test to see if it CAN to an exclusive lock (the lock file is not already locked).
        if (unlocked) { lock
        No, you've got a race condition there. Two processes can both see "unlocked" at the same time, then they both "lock" and they think they've got it.

        That's why you need "atomic test and set". There's really no way around it. The thing that checks for unsettedness needs to also set it if it can, in a way that can't be interrrupted or observed by another process/thread/entity. This usually requires operating-system-level locks.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

        To make merlyn's point clearer, what you need is
        sleep(rand) until lock(exclusive); print close

        The lock() function must return whether it was able to get a lock, and you must not use a separate is_locked() function in this process. Actually, you should never need one at all, because you know when you have the lock and you're not interested in whether someone else has one if you don't have it.

        sleep() is only necessary if your locking function does not block. rand() must be used if this is only one of several locks or other synchronization mechanisms used by multiple processes, because they might otherwise get in a deadlock situation when each process holds a lock on some of the resources but not on others.

        You may also unlock() instead of close() if you know what you're doing, but it has only become safe in recent versions of Perl, and even so it poses the danger that you'll attempt to read data from the file while another process is writing to it. Ideally, you should lock(shared) if you're going to be reading from the file, and the locking mechanism should honour exclusive lock requests from others before your lock degradation request. In that way, no writing process starves, while no reading process gets to read inconsistent data.

        Do not underestimate the complexity of locking. Unfortunately if you don't get it perfect, you might as well not bother at all.

        Makeshifts last the longest.

Re: Logical Conundrum
by hardburn (Abbot) on Dec 01, 2003 at 16:01 UTC
    my $lock = 1; $SIG{CONT} = sub { $lock = 0 }; while($lock) { sleep 1 } # Rest of your program

    Then you send a CONT (continue) signal to the process when you want it to go. Probably not the best solution, as signal catching is considered a last-resort situation.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

Re: Logical Conundrum (processes)
by tye (Sage) on Dec 01, 2003 at 18:23 UTC

    Part of the point of separate processes is that they cannot access each other's memory. So no matter how you pass \$lock to a different process, it won't be able to do anything useful with it.

    You need to use flock (or something similar) but on a different file for each user and this filename is what you pass to each subprocess, not \$lock. I'd use File::Temp to create these lock files safely.

    Update: The original node says:

    flock() is not an option for this, as this program will need to be able to be run by multiple users at the same time.
    hence my suggestion and why I don't think merlyn's reply is in context.

                    - tye
        $0 may be unique to the script, but that is a BIG problem. What I have is a main program (parent) spawning a number of child programs. Each of the child programs is trying to print to <STDOUT>. What I am having is when 2 children are trying to print to <STDOUT> at the same time, the information gets mixed. By trying to set flock(), I am trying to prevent one child from printing to <STDOUT> while another child is in the middle of doing that. The end result should be that I don't have any mixed outputs.

        Problem is, the traditional uses of flock() aren't helping, they are creating either the same problem with the output, or they are creating runaway processes.

        I've created a file, "lock_file". I have a lock_ex subroutine that returns a file handle when it can create an exclusive lock. I need to check to see if that is in place, if so wait, if not go ahead and print to <STDOUT>.

Re: Logical Conundrum
by tadman (Prior) on Dec 01, 2003 at 15:15 UTC
    How are you creating these parallel programs? fork()? There's a variety of ways you can go about creating these parallel functions, including threads, but each have their own mechanism for inter-process communication.

    You might have to read up on perlipc.
Re: Logical Conundrum
by BrowserUk (Patriarch) on Dec 01, 2003 at 19:09 UTC

    If I were doing this on Win32, I'd look at using Win32::Semaphore.

    There is IPC::Semaphore which does a similar thing under *nix, but I don't have any experience of it, so I don't know if it could be adapted for your purposes if that is your platform.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!
    Wanted!