Производитель/потребитель C#

При отладке вывода может оказаться полезным использовать формат шестнадцатеричного дампа, который показывает ASCII-представление байтов в правом столбце. Я использую xxd для этого и рекомендую вам сделать то же самое. В частности, для шрифтов OpenType этот формат дампа очень удобен для выявления проблем с выравниванием, особенно в заголовке шрифта.

Чтобы понять, что я имею в виду: вот первый раздел одного из ваших дампов:

00000000: 4f54 544f 0009 0008 0003 0001 4346 4620  OTTO........CFF 
00000010: 0000 0000 0000 00c2 9c00 0000 0563 6d61  .............cma
00000020: 7000 0000 0000 0000 c2a4 0000 0024 6865  p............$he
00000030: 6164 0000 0000 0000 00c3 8800 0000 3668  ad............6h
00000040: 6865 6100 0000 0000 0001 0000 0000 2468  hea...........$h
00000050: 6d74 7800 0000 0000 0001 2400 0000 006d  mtx.......$....m
00000060: 6178 7000 0000 0000 0001 2400 0000 066e  axp.......$....n
00000070: 616d 6500 0000 0000 0001 2c00 0000 064f  ame.......,....O
00000080: 532f 3200 0000 0000 0001 3400 0000 6470  S/2.......4...dp
00000090: 6f73 7400 0000 0000 0001 c298 0000 0020  ost............ 

Вы можете видеть, что первая запись tableDirectory для таблицы CFF - тег ('CFF') ) начинается как последние 4 байта первой строки (4346 4620, что в ASCII - «CFF»), и это правильное выравнивание. Поскольку длина строки hexdump составляет ровно 16 байтов, а каждая запись в tableDirectory составляет 16 байтов, должным образом выровненный tableDirectory будет иметь теги для каждой таблицы как последние 4 байта строки. Но, как вы можете видеть, у вас есть некоторое смещение ... 'c' (0x63) для 'cmap' находится на один байт слишком далеко вправо, и последующие записи tableDirectory также сдвигаются. Он должен выглядеть примерно так:

00000000: 4f54 544f 0009 0008 0003 0001 4346 4620  OTTO........CFF 
00000010: ???? ???? ???? ???? ???? ???? 636d 6170  ............cmap
00000020: ???? ???? ???? ???? ???? ???? 6865 6164  ............head
00000030: ???? ???? ???? ???? ???? ???? 6868 6561  ............hhea
[...]

Так что похоже на некоторые ваших записей в tableDirectory, в конце добавляется дополнительный байт. Поэтому вам нужно выполнить резервное копирование и проверить этот код, чтобы он сначала работал правильно, а затем позаботиться о searchRange, rangeShift и entrySelector (который, предполагая, что ваш шрифт содержит 9 таблиц, как объявлено, должен быть: 0080 0003 0010 (hex)).

26
задан razlebe 24 February 2011 в 13:42
поделиться

4 ответа

Код старше этого - я написал его за некоторое время до выхода .NET 2.0. Концепция очереди производителя / потребителя на намного старше, чем эта:)

Да, этот код безопасен, насколько мне известно, - но это имеет некоторые недостатки:

  • Это не является общим. Современная версия, безусловно, будет общей.
  • У него нет возможности остановить очередь. Один простой способ остановить очередь (чтобы все потоки потребителя удалились) - это получить токен «прекращение работы», который можно поместить в очередь. Затем вы добавляете столько токенов, сколько у вас есть потоков. Кроме того, у вас есть отдельный флаг, чтобы указать, что вы хотите остановить. (Это позволяет другим потокам остановиться перед завершением всей текущей работы в очереди.)
  • Если задания очень малы, использование одного задания за раз может быть не самым эффективным способом.

Идеи, стоящие за кодом, честно говоря, важнее самого кода.

33
ответ дан Jon Skeet 28 November 2019 в 06:18
поделиться

В тот день я узнал, как работает Monitor.Wait / Pulse (и много о потоках в целом) из приведенного выше фрагмента кода и серии статей , из которой он сделан. Поэтому, как говорит Джон, он имеет большое значение и действительно безопасен и применим.

Однако, начиная с .NET 4, в структуре имеется реализация очереди производитель-потребитель . Я только нашел это сам, но до этого момента он делает все, что мне нужно.

17
ответ дан kicsit 28 November 2019 в 06:18
поделиться

Предупреждение: Если вы прочитаете комментарии, вы поймете, что мой ответ неверен:)

В вашем коде возможен тупик .

Представьте себе следующий случай, для ясности, я использовал однопоточный подход, но его должно быть легко преобразовать в многопоточность с помощью сна:

// We create some actions...
object locker = new object();

Action action1 = () => {
    lock (locker)
    {
        System.Threading.Monitor.Wait(locker);
        Console.WriteLine("This is action1");
    }
};

Action action2 = () => {
    lock (locker)
    {
        System.Threading.Monitor.Wait(locker);
        Console.WriteLine("This is action2");
    }
};

// ... (stuff happens, etc.)

// Imagine both actions were running
// and there's 0 items in the queue

// And now the producer kicks in...
lock (locker)
{
    // This would add a job to the queue

    Console.WriteLine("Pulse now!");
    System.Threading.Monitor.Pulse(locker);
}

// ... (more stuff)
// and the actions finish now!

Console.WriteLine("Consume action!");
action1(); // Oops... they're locked...
action2();

Пожалуйста, дайте мне знать, если это не так имеет смысл.

Если это подтвердится, то ответ на ваш вопрос: «Нет, это не безопасно»;) Надеюсь, это поможет.

0
ответ дан DiogoNeves 28 November 2019 в 06:18
поделиться

Вы можете сделать что-то вроде следующего фрагмента кода. Он общий и имеет метод постановки нулей в очередь (или любой другой флаг, который вы хотите использовать), чтобы сообщить рабочим потокам о выходе.

Код взят отсюда: http: //www.albahari .com / threading / part4.aspx # _Wait_and_Pulse

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

namespace ConsoleApplication1
{

    public class TaskQueue<T> : IDisposable where T : class
    {
        object locker = new object();
        Thread[] workers;
        Queue<T> taskQ = new Queue<T>();

        public TaskQueue(int workerCount)
        {
            workers = new Thread[workerCount];

            // Create and start a separate thread for each worker
            for (int i = 0; i < workerCount; i++)
                (workers[i] = new Thread(Consume)).Start();
        }

        public void Dispose()
        {
            // Enqueue one null task per worker to make each exit.
            foreach (Thread worker in workers) EnqueueTask(null);
            foreach (Thread worker in workers) worker.Join();
        }

        public void EnqueueTask(T task)
        {
            lock (locker)
            {
                taskQ.Enqueue(task);
                Monitor.PulseAll(locker);
            }
        }

        void Consume()
        {
            while (true)
            {
                T task;
                lock (locker)
                {
                    while (taskQ.Count == 0) Monitor.Wait(locker);
                    task = taskQ.Dequeue();
                }
                if (task == null) return;         // This signals our exit
                Console.Write(task);
                Thread.Sleep(1000);              // Simulate time-consuming task
            }
        }
    }
}
28
ответ дан 28 November 2019 в 06:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: