using System;
using System.Collections.Generic;
using System.Threading;

class OutputCollector
{
    static OutputCollector()
    {
        instance = new OutputCollector();
    }
    private OutputCollector()
    {
        this.outputLines = new List<string>();
        this.outputLock = new object();
        this.outputAvailableOrExiting = new ConditionVariable();
    }
    public static OutputCollector Instance
    {
        get { return instance; }
    }
    public void WriteLine(string line)
    {
        lock (outputLock)
        {
            outputLines.Add(line);
        }
        outputAvailableOrExiting.NotifyOne();
    }
    public void Run()
    {
        try
        {
            int lineNumber = 1;
            while (!exiting)
            {
                lock (outputLock)
                {
                    outputAvailableOrExiting.Wait(outputLock, OutputLinesAvailableOrExiting, null);
                    while (outputLines.Count > 0)
                    {
                        string outputLine = outputLines[0];
                        outputLines.RemoveAt(0);
                        Console.WriteLine(lineNumber.ToString() + ": " + outputLine);
                        ++lineNumber;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.ToString());
        }
    }
    public void Exit()
    {
        lock (outputLock)
        {
            exiting = true;
            outputAvailableOrExiting.NotifyOne();
        }
    }
    private bool OutputLinesAvailableOrExiting(object arg)
    {
        return outputLines.Count > 0 || exiting;
    }
    private static OutputCollector instance;
    private List<string> outputLines;
    private object outputLock;
    private bool exiting;
    private ConditionVariable outputAvailableOrExiting;
}

void OutputThreadMain(object arg)
{
    try
    {
        int threadId = cast<int>(arg);
        for (int i = 0; i < 10; ++i)
        {
            OutputCollector.Instance.WriteLine("hello " + i.ToString() + 
                " from thread " + threadId.ToString());
        }
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine(ex.ToString());
    }
}

void main()
{
    try
    {
        List<Thread> threads = new List<Thread>();
        Thread outputCollectorThread = Thread.StartMethod(OutputCollector.Instance.Run);
        int n = 2 * HardwareConcurrency();
        for (int i = 0; i < n; ++i)
        {
            Thread outputThread = Thread.StartFunction(OutputThreadMain, i);
            threads.Add(outputThread);
        }
        foreach (Thread thread in threads)
        {
            thread.Join();
        }
        OutputCollector.Instance.Exit();
        outputCollectorThread.Join();
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine(ex.ToString());
    }
}