Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternating threads #2

Imagine a situation in which there are one king and n number of minions submitted to him. When the king says "One!", one of the minions says "Two!", but only one of them. That is, only the fastest minion speaks while the others must wait for another call of the king.

This is my try:

using System;
using System.Threading;

class Program {
    static bool leaderGO = false;

    void Leader() {
        do {
            lock(this) {
                //Console.WriteLine("? {0}", leaderGO);

                if (leaderGO) Monitor.Wait(this);

                Console.WriteLine("> One!");
                Thread.Sleep(200);
                leaderGO = true;

                Monitor.Pulse(this);
            }
        } while(true);
    }

    void Follower (char chant) {
        do {
            lock(this) {
                //Console.WriteLine("! {0}", leaderGO);

                if (!leaderGO) Monitor.Wait(this);

                Console.WriteLine("{0} Two!", chant);
                leaderGO = false;

                Monitor.Pulse(this);
            }
        } while(true);
    }

    static void Main() {
        Console.WriteLine("Go!\n");

        Program m = new Program();

        Thread king = new Thread(() => m.Leader());

        Thread minion1 = new Thread(() => m.Follower('#'));
        Thread minion2 = new Thread(() => m.Follower('$'));

        king.Start();

        minion1.Start();
        minion2.Start();

        Console.ReadKey();
        king.Abort();
        minion1.Abort();
        minion2.Abort();
    }
}

The expected output would be this (# and $ representing the two different minions):

> One!
# Two!
> One!
$ Two!
> One!
$ Two!

...

The order in which they'd appear doesn't matter, it'd be random. The problem, however, is that this code, when compiled, produces this instead:

> One!
# Two!
$ Two!
> One!
# Two!
> One!
$ Two!
# Two!

...

That is, more than one minion speaks at the same time. This would cause quite the tumult with even more minions, and a king shouldn't allow a meddling of this kind.

What would be a possible solution?


For future readers, here's the final, working code:

using System;
using System.Threading;

class Program { 
    static AutoResetEvent leader = new AutoResetEvent(false);
    static AutoResetEvent follower = new AutoResetEvent(false);

    void Leader() {
        do {
            Console.WriteLine("  One!");
            Thread.Sleep(300);

            follower.Set();     // Leader allows a follower speak
            leader.WaitOne();   // Leader waits for the follower to finish speaking
        } while(true);
    }

    void Follower (char emblem) {
        do {
            follower.WaitOne();     // Follower waits for the leader to allow speaking
            Console.WriteLine("{0} Two!", emblem);
            leader.Set();           // Follower finishes speaking
        } while(true);
    }

    static void Main() {
        Console.WriteLine("Go!\n");

        Program m = new Program();

        Thread king = new Thread(() => m.Leader());

        Thread minion1 = new Thread(() => m.Follower('#'));
        Thread minion2 = new Thread(() => m.Follower('$'));
        Thread minion3 = new Thread(() => m.Follower('&'));

        king.Start();

        minion1.Start();
        minion2.Start();
        minion3.Start();

        Console.ReadKey();
        king.Abort();
        minion1.Abort();
        minion2.Abort();
        minion3.Abort();
    }
}
like image 810
Mutoh Avatar asked Jan 14 '23 23:01

Mutoh


2 Answers

Try using an AutoResetEvent instead of a lock/monitor. It allows you to create a "gate" that only one thread can get through at a time.

Your Follower() threads would call event.WaitOne() (optionally with a timeout). Your Leader() function would call event.Set(), which will release one of the waiting threads.

An AutoResetEvent (as opposed to other types of wait handles) will automatically "close the gate" once of the waiting threads is through.

http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx

like image 119
atkretsch Avatar answered Jan 21 '23 15:01

atkretsch


You aren't locking the followers down. So both threads are seeing leaderGo are true, and respond. Have the thread lock itself down before writing out, and that should fix it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Threading
{
    class Program
    {
    static bool leaderGO = false;
    static bool followerGo = false;

    void Leader()
    {
        do
        {
            lock (this)
            {
                //Console.WriteLine("? {0}", leaderGO);

                if (leaderGO) Monitor.Wait(this);

                Console.WriteLine("> One!");
                Thread.Sleep(200);
                leaderGO = true;
                followerGo = true;

                Monitor.Pulse(this);
            }
        } while (true);
    }

    void Follower(char chant)
    {
        do
        {
            lock (this)
            {
                //Console.WriteLine("! {0}", leaderGO);

                if (!leaderGO) Monitor.Wait(this);

                if(followerGo)
                {
                    followerGo = false;
                    Console.WriteLine("{0} Two!", chant);
                    leaderGO = false;
                }

                Monitor.Pulse(this);
            }
        } while (true);
    }

    static void Main()
    {
        Console.WriteLine("Go!\n");

        Program m = new Program();

        Thread king = new Thread(() => m.Leader());

        Thread minion1 = new Thread(() => m.Follower('#'));
        Thread minion2 = new Thread(() => m.Follower('$'));

        king.Start();

        minion1.Start();
        minion2.Start();

        Console.ReadKey();
        king.Abort();
        minion1.Abort();
        minion2.Abort();
    }
}

}

like image 40
Alexander Matusiak Avatar answered Jan 21 '23 16:01

Alexander Matusiak