Доступ к трем спискам с тремя потоками, печать элементов по порядку

Вот вопрос:

Скажем, есть 3 списка l1, l2 и l3 одинаковой длины. Три потока обращаются к трем спискам. Произнесите T1 -> l1, T2 -> l2 и T3 -> l3. Он должен печатать в порядке, скажем, первый элемент 1-го списка, затем первый элемент 2-го списка, а затем первый элемент 3-го списка. Затем второй элемент 1-го, затем второй элемент 2-го списка, а затем второй элемент 3-го списка.

Что я пробовал:

 private static readonly Object obj = new Object();
    static List<string> list1 = new List<string> { "1", "2", "3", "4" };
    static List<string> list2 = new List<string> { "a", "b", "c", "d" };
    static List<string> list3 = new List<string> { "*", "+", "-", "?" };
    static int i = 0;
    static void Main(string[] args)
    {
        Thread t1 = new Thread(() => PrintItem());
        t1.Name = "Print1";
        Thread t2 = new Thread(() => PrintItem());
        t2.Name = "Print2";
        Thread t3 = new Thread(() => PrintItem());
        t3.Name = "Print3";
        t1.Start();
        t2.Start();
        t3.Start();
        t1.Join();
        t2.Join();
        t3.Join();
        Console.Read();
    }

    private static void PrintItem()
    {
        while (true)
        {
            lock (obj)
            {
                if (i >= list1.Count)
                    break;
                Console.WriteLine(Thread.CurrentThread.Name + " " + list1[i]);
                Console.WriteLine(Thread.CurrentThread.Name + " " + list2[i]);
                Console.WriteLine(Thread.CurrentThread.Name + " " + list3[i]);
                i++;
            }
        }
    }

Вывод правильный, но он не использует три потока. Исправьте код пожалуйста.


person Hui Zhao    schedule 11.12.2014    source источник
comment
Ваши требования запрещают выполнение каких-либо действий параллельно между вашими потоками. В каждый момент времени существует только один поток, который может что-либо делать. Учитывая это, вам намного лучше просто не иметь несколько потоков и использовать только один поток. То есть, если вы не можете ослабить свои требования, чтобы фактически позволить им работать параллельно.   -  person Servy    schedule 11.12.2014
comment
@Servy, я получил этот вопрос из Интернета. Это от вопросы технических руководителей Novell   -  person Hui Zhao    schedule 11.12.2014
comment
Хорошо. Это не делает его совершенно бессмысленным набором требований.   -  person Servy    schedule 11.12.2014


Ответы (3)


Если бы это был я, я бы использовал EventWaitHandles для сигнализации и синхронизации ваших потоков. В моем коде потоки ждут сигнала, а затем распечатывают свою текущую строку, а затем снова ждут сигнала:

    static void Main(string[] args)
    {
        List<string> list1 = new List<string> { "1", "2", "3", "4" };
        List<string> list2 = new List<string> { "a", "b", "c", "d" };
        List<string> list3 = new List<string> { "*", "+", "-", "?" };


        using (EventWaitHandle waitHandle1 = new AutoResetEvent(false))
        using (EventWaitHandle waitHandle2 = new AutoResetEvent(false))
        using (EventWaitHandle waitHandle3 = new AutoResetEvent(false))
        using (EventWaitHandle waitHandle4 = new AutoResetEvent(false))
        {
            Thread t1 = new Thread(() => 
            {
                ThreadData state = new ThreadData() 
                { 
                    Name = "Thread1", 
                    Strings = list1, 
                    WaitHandle = waitHandle1, 
                    SignalHandle = waitHandle2 
                };

                PrintItemWhenSignaled(state);
            });

            Thread t2 = new Thread(() => 
            {
                ThreadData state = new ThreadData()
                {
                    Name = "Thread2",
                    Strings = list2,
                    WaitHandle = waitHandle2,
                    SignalHandle = waitHandle3
                };

                PrintItemWhenSignaled(state);
            });

            Thread t3 = new Thread(() =>
            {
                ThreadData state = new ThreadData()
                {
                    Name = "Thread3",
                    Strings = list3,
                    WaitHandle = waitHandle3,
                    SignalHandle = waitHandle4
                };

                PrintItemWhenSignaled(state);
            });


            t1.Start();
            t2.Start();
            t3.Start();

            for (int index = 0; index < list1.Count; index++)
            {
                waitHandle1.Set();
                waitHandle4.WaitOne(100);
            }
        }

        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }

    private static void PrintItemWhenSignaled(ThreadData threadState)
    {
        foreach (string value in threadState.Strings)
        {
            threadState.WaitHandle.WaitOne(100);
            Console.WriteLine("{0}:{1}", threadState.Name, value);
            threadState.SignalHandle.Set();
        }
    }

    public class ThreadData
    {
        public string Name { get; set; }
        public EventWaitHandle WaitHandle { get; set; }
        public EventWaitHandle SignalHandle { get; set; }
        public List<string> Strings { get; set; }
    }
}
person Mike Burdick    schedule 11.12.2014
comment
Если у нас есть 10000 элементов в списке, нужно ли нам определять 10000 EventWaitHandle? - person Hui Zhao; 11.12.2014
comment
Нет, только каждый создаваемый вами поток требует EventWaitHandle, а вызывающему потоку требуется EventWaitHandle, поэтому в вашем примере вам нужно 4. - person Mike Burdick; 11.12.2014
comment
Можете ли вы обновить код? Я хочу напечатать имя или идентификатор потока, чтобы убедиться, что он использует три потока. Текущий код печатает только значения. Также я хотел бы использовать выражение лямбада. - person Hui Zhao; 11.12.2014
comment
Вы можете сделать это всего с тремя дескрипторами ожидания, если сделаете поток 3 уведомляющим WaitHandle1. Таким образом, основной поток не должен вмешиваться. Если главному потоку нужно знать, когда все потоки завершились, все, что ему нужно сделать, это Join последний поток. - person Jim Mischel; 11.12.2014

Хотя это очень странное требование, хороший масштабируемый способ может быть с Monitor.Pulse и Monitor.Wait

    private static readonly Object obj = new Object();
    static List<string> list1 = new List<string> { "1", "2", "3", "4" };
    static List<string> list2 = new List<string> { "a", "b", "c", "d", "e" };
    static List<string> list3 = new List<string> { "*", "+", "-", "?" };
    static int i = 0;

    //thread synchronization data
    const int numThreads = 3;
    static int workingCount = 0;
    static int lastItem = 0;
    static object locker = new object();

    static void Main(string[] args)
    {
        Thread t1 = new Thread(PrintItem);
        t1.Name = "Print1";
        Thread t2 = new Thread(PrintItem);
        t2.Name = "Print2";
        Thread t3 = new Thread(PrintItem);
        t3.Name = "Print3";
        t1.Start(0);
        t2.Start(1);
        t3.Start(2);
        t1.Join();
        t2.Join();
        t3.Join();
        Console.ReadLine();
    }

    private static void PrintItem(object state)
    {
        Interlocked.Increment(ref workingCount);
        int workingList = (int)state;
        int idx = 0;
        List<string> list = null;
        switch (workingList)
        {
            case 0:
                list = list1;
                break;
            case 1:
                list = list2;
                break;
            case 2:
                list = list3;
                break;
        }


        lock (locker)
            do
            {
                while ((lastItem % numThreads) != workingList)
                {
                    Monitor.Wait(locker);
                }

                Console.WriteLine("Thread: {0}\tValue: {1}", Thread.CurrentThread.Name, list[idx]);
                lastItem++;
                Monitor.PulseAll(locker);

            } while (++idx < list.Count);

        //Handle continuing to pulse until all lists are done.
        Interlocked.Decrement(ref workingCount);

        lock (locker)
            while (workingCount != 0)
            {
                while ((lastItem % numThreads) != workingList)
                    Monitor.Wait(locker);
                lastItem++;
                Monitor.PulseAll(locker);
            }

    }
}

введите здесь описание изображения

person bigtlb    schedule 11.12.2014
comment
Это предполагает, что все списки имеют одинаковый размер. В противном случае нам пришлось бы добавить немного логики, чтобы завершенные потоки продолжали плюс до тех пор, пока не будут выполнены все списки. Вероятно, с обратным отсчетом InterlockedIncrement. - person bigtlb; 11.12.2014
comment
Название темы не было напечатано. Пример: Print1. Но я просто проголосовал за вас из-за лаконичного кода. - person Hui Zhao; 11.12.2014
comment
@HuiZhao Thread.CurrentThread.Name печатает имя текущего потока. Я приложу скриншот выше. - person bigtlb; 11.12.2014
comment
Упс, дерьмо. Прошу прещения за это. Я забыл установить в качестве проекта в качестве запуска. Он запускает старый. - person Hui Zhao; 11.12.2014
comment
Без проблем. Если ваши списки могут быть разной длины, я изменил код, чтобы он изящно вращался вниз. Итак, если в List1 заканчиваются элементы, то List 2 и 3 будут продолжаться... - person bigtlb; 11.12.2014

из-за блокировки только один поток ... самый быстрый - первый ... может получить доступ к вашему списку.

удалите оператор блокировки или удалите:

        if (i >= list1.Count)
                break;

условие или создайте i ThreadStatic с атрибутом ThreadStatic

если это ваш ожидаемый результат? введите здесь описание изображения

person Venson    schedule 11.12.2014
comment
Это не работает, удаление lock вызовет исключение. Я проверил это. - person Hui Zhao; 11.12.2014
comment
Какое исключение? попробуйте создать переменную ThreadStatic, чтобы у каждого потока была собственная переменная uniq. этот код будет выполняться только один раз по списку, и когда следующий поток попытается выполнить код, условие вызовет остановку. - person Venson; 11.12.2014
comment
Я обновил сообщение, чтобы показать вам использование этого атрибута и результат, который я получил. - person Venson; 11.12.2014
comment
Код не является безопасным и (надежно) не создает требуемый результат без какой-либо синхронизации. - person Servy; 11.12.2014
comment
Пожалуйста, объясните подробнее, я не понимаю ... когда я читаю описание, я ожидал именно этого. Попробуйте создать ProcessDiagram - person Venson; 11.12.2014