Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.69/13: Рейтинг темы: голосов - 13, средняя оценка - 4.69
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
1

Многопоточная генерация сигнатуры файла

15.01.2017, 12:30. Показов 2491. Ответов 12
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Здравствуйте. Подскажите как лучше решить задачу и в каком направлении копать. С многопоточностью практически не знаком.
Есть задание:
Требуется написать консольную программу на C# для генерации сигнатуры указанного файла. Сигнатура генерируется следующим образом: исходный файл делится на блоки заданной длины (кроме последнего блока), для каждого блока вычисляется значение hash-функции SHA256, и вместе с его номером выводится в консоль.
Набросал кое-какое решение, но оно меня совсем не устраивает. Тестироваться это будет на очень больших файлах (0-32гб). Я проверял свое решение на сгенерированном текстовом файле размером 5гб. Т.к у меня процессор 4х ядерный я использовал 4 потока: 1 - главный и 3 рабочих. Каждый поток читает указанное к-во байт (блок) с помощью BinaryReader, получает хэш этого блока и выводит на экран номер блока и хэш. Номер блока каждый поток получает из свойства которое выдает ему номер блока и увеличивается на 1. Для того чтоб несколько потоков не получили один и тот-же номер я блокирую объектную переменную созданную специально для этих целей (предполагаю эта часть и тормозит выполнение программы). Потестировав программу я понял что в 1 поток она работает в разы быстрее чем в 4 (пробовал увеличивать размер блока, но результат тот-же). Вопрос: как и можно ли добиться полезного использования 4 ядер для решения этой задачи? Правильно ли я подозреваю что 4 потока работают медленней из-за блокировки? Пока ковыряю литературу по многопоточности, но может кто-то сможет направить меня по верному пути. Код программы ниже.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    class Controller
    {
        //Хранит текущую точку чтения файла
        int _blockNumber;
        //Хранит предыдущее значение _blockNumber
        int _temp;
        //Длинна буфера
        int _bufferSize;
        //путь к файлу для чтения
        string _filePath;
        //Длинна файла
        long _fileLength;
 
        //Объект который будем блокировать
        object _toLock = new object();
 
        //Номер блока
        private int blockNumber
        {
            get
            {
                _temp = _blockNumber;
                _blockNumber++;
                return _temp;
            }
        }
 
        public Controller(string filePath, int bufferSize)
        {
            _bufferSize = bufferSize;
            _filePath = filePath;
 
            FileInfo file = new FileInfo(filePath);
            _fileLength = file.Length;
 
            if (!file.Exists)
            {
                Console.WriteLine("Файл по указанному пути не найден");
                return;
            }
 
            //Получаем к-во процессоров/ядер
            int _threadsCount = Environment.ProcessorCount;
            //Один поток главный, все остальное работники
            _threadsCount = _threadsCount > 2 ? _threadsCount - 1 : 1;
 
            for (int i = 0; i < _threadsCount; i++)
                new Thread(new ThreadStart(TODO)).Start();
        }
 
        //Пока не решил как назову этот метод :)
        private void TODO()
        {
            while (true)
            {
                long currentPoint;
 
                lock (_toLock)
                {
                    currentPoint = blockNumber;
                }
 
                if (currentPoint * _bufferSize > _fileLength)
                    return;
 
                byte[] _buffer = new byte[_bufferSize];
 
                using (BinaryReader br = new BinaryReader(File.Open(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read)))
                {
                    try
                    {
                        br.BaseStream.Position = currentPoint * _bufferSize;
                        br.Read(_buffer, 0, _bufferSize);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
                    }
                }
 
                SHA256 sha = SHA256Managed.Create();
 
                byte[] hash = sha.ComputeHash(_buffer);
 
                Console.Clear();
                Console.Write(currentPoint + " - ");
                PrintByteArray(hash);
            }
        }
 
        public static void PrintByteArray(byte[] array)
        {
            int i;
            for (i = 0; i < array.Length; i++)
            {
                Console.Write(String.Format("{0:X2}", array[i]));
                if ((i % 4) == 3) Console.Write(" ");
            }
            Console.WriteLine();
        }
    }
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
15.01.2017, 12:30
Ответы с готовыми решениями:

Многопоточная загрузка файла
Всем доброго времени суток! Помогите пожалуйста с реализацией метода для многопоточного скачивания....

Генерация сигнатуры файла. Потеря памяти
Нужно написать программу, которая будет генерировать сигнатуру. Файл делим на заданные блоки и их...

Проверка сигнатуры файла
нужно организовать проверку сигнатуры JPG файла при его открытии

Многопоточная обработка файла
Всем доброго времени суток! У меня есть задача параллельной обработки файла некоторым...

12
Эксперт .NET
12073 / 8383 / 1280
Регистрация: 21.01.2016
Сообщений: 31,578
15.01.2017, 13:30 2
V0vchik, жёсткому диску наплевать сколько у процессора ядер. У него фиксированная пропускная способность, которую ты поделил на четыре запустив задачу в несколько потоков. С диском работай только в одном потоке (одновременно, я имею в виду).

Я вижу это так: прочитать с диска блок данных (по-больше), и в четыре потока его посчитать (ну, тоесть разбить его на четыре блока). Единственное, что я точно не знаю как можно хеш посчитать многопоточно. Тут я не силён. Может действительно можно сделать так как это раньше делали всякие P2P клиенты типа FlylinkDC++ и ему подобные, когда TTR считали: бить данные на блоки и для каждого в отдельности считать хеш. Это можно и в параллель сделать. Но с диском всёравно нужно в один поток работать.
1
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
15.01.2017, 13:45  [ТС] 3
Usaga, ок, попробую. Спасибо за советы. Сейчас возникла мысль читать одним потоком в небольшой массив который будет очередью блоков, а из него уже остальными потоками брать данные по очереди и их обрабатывать. Правда я не знаю на сколько это удачная идея. Уже начал тестировать эту задумку.
0
Эксперт .NET
12073 / 8383 / 1280
Регистрация: 21.01.2016
Сообщений: 31,578
15.01.2017, 14:09 4
V0vchik, лучше создать четыре потока-воркера (worker), и один объект-поставщик данных. Все потоки будут к нему ломиться за данными, а он будет поочерёдно считывать данные блоками (по одному) и раздавать воркерам. Получится, что пока поставщик считывает новый блок над предыдущим (предыдущими) уже ведётся работа. Распараллеливание получается лучше, чем все потоки будут дружно ждать данных.

Добавлено через 5 минут
Это классическая задача Producer-Consumer. Реашется массой разных способов. Вот есть некоторые примеры. Но можно нагуглить ещё пару-тройку миллионов.
1
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
15.01.2017, 14:13  [ТС] 5
Usaga, это тоже обязательно попробую, но с начала хочу потестить какой результат даст эта задумка. Спасибо.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
15.01.2017, 16:36 6
V0vchik, я писал решение для такой задачи под .Net 3.5. Правда я его мало тестировал. Можете посмотреть решение тут: https://github.com/UnresolvedE... eSignature
Решение было навеяно этой темой: Прошу консультации экспертов (ООП, многопоточность)
1
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
16.01.2017, 05:20  [ТС] 7
TopLayer, пока не смог разобрать ваш пример, как и многие из той темы. Явно не хватает знаний

Со 2 попытки вышло не лучше чем в первый раз:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
 
namespace Test1V3
{
    class Program
    {
        static void Main(string[] args)
        {
            something s = new something("d:/123.txt", 1000);
        }
    }
 
    class something
    {
        //Длинна буфера
        int _bufferSize;
        //путь к файлу для чтения
        string _filePath;
        //Длинна файла
        long _fileLength;
        //Текущая позиция чтения
        long _blocknumber;
 
        //Объект который будем блокировать
        object _toLock = new object();
 
        protected struct block
        {
            public long Number;
            public byte[] Data;
 
            public block(long number, byte[] data)
            {
                Number = number;
                Data = data;
            }
        }
 
        public something(string filePath, int bufferSize)
        {
            _filePath = filePath;
            _bufferSize = bufferSize;
 
            FileInfo file = new FileInfo(filePath);
            _fileLength = file.Length;
 
            //Получаем к-во процессоров/ядер
            int _threadsCount = Environment.ProcessorCount; ;
 
            for (int i = 0; i < _threadsCount; i++)
            {
                new Thread(new ThreadStart(TODO)).Start();
            }
 
            Console.ReadKey();
        }
 
        private void TODO()
        {
            while (true)
            {
                block dataBlock = GetData();
 
                if (_bufferSize >= 20000000)
                    GC.Collect();
 
                if (dataBlock.Number < 0)
                    return;
 
                SHA256 sha = SHA256Managed.Create();
 
                byte[] hash = sha.ComputeHash(dataBlock.Data);
 
                Console.Clear();
                Console.Write(dataBlock.Number + " - ");
                PrintByteArray(hash);
            }
        }
 
        public static void PrintByteArray(byte[] array)
        {
            int i;
            for (i = 0; i < array.Length; i++)
            {
                Console.Write(String.Format("{0:X2}", array[i]));
                if ((i % 4) == 3) Console.Write(" ");
            }
            Console.WriteLine();
        }
 
        protected block GetData()
        {
            block temp = new block(-1, new byte[_bufferSize]);
            long position = _blocknumber * _bufferSize;
 
            lock (_toLock)
            {
                using (BinaryReader br = new BinaryReader(File.Open(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read)))
                {
                    if (position > _fileLength)
                        return temp;
 
                    try
                    {
                        br.BaseStream.Position = position;
                        br.Read(temp.Data, 0, _bufferSize);
                        temp.Number = _blocknumber;
                        _blocknumber++;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
                    }
                }
 
                return temp;
            }
        }
    }
}
Буду дальше ковырять примеры. Самому решить не получается.
0
Эксперт .NET
12073 / 8383 / 1280
Регистрация: 21.01.2016
Сообщений: 31,578
16.01.2017, 06:30 8
V0vchik, тут синхронизация не обязательна. Можно использовать потокобезопасную коллекцию типа BlockCollection. Погугли примеры её использования, в сети их очень много.

Так же рекомендую переиспользовать блоки данных, чтобы не пересоздавать их каждый раз и не напрягать сборщик мусора. К примеру создать пул блоков (по одному на поток, можно и больше), из которых поставщик данных будет брать свободные блоки, а воркеры будут блоки в пул возвращать.

И ещё: вызов GC.Collect() - есть очень плохая практика. GC и без тебя знает, когда ему запуститься.

И названия классам давай говорящие. something и todo мало что говорят о предназначении этих сущностей.
0
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
16.01.2017, 06:45  [ТС] 9
Usaga, ну это они пока так называются А GC.Collect() тут не просто так. Когда размер блока больше 20мб он не успевает вовремя очиститься. Может потому-что у меня не самый быстрый компьютер, но у меня процесс этого приложения постепенно растет от 100мб до 1гб, потом видимо происходит сборка и он опять растет до 1гб. Это при блоке в 20мб, а если больше там дела совсем плохо. С GC.Collect() все чиститься вовремя. Про BlockCollection почитаю, спасибо
0
Эксперт .NET
12073 / 8383 / 1280
Регистрация: 21.01.2016
Сообщений: 31,578
16.01.2017, 06:52 10
V0vchik, 20Мб - слишком много. Пары мегабайт вполне себе хватит. И сборка будет чаще и потокам задача в руки попадать чаще будет. А так получается, что все потоки тупо сидят и ждут пока ты 20мб с диска поднимешь только для одного из них. Я больше чем уверен, что поток успеет быстрее обсчитать этот блок, чем этот же блок с диска поднимется.

В общем, перефразирую прошлый пост: если твой код не может нормально работать без GC.Collect(), то такой код - фигня и его нужно переписать. Меньший размер блоков, пул блоков (переиспользование) и всё пучком.

Добавлено через 2 минуты
Т.е. тебе нужны две потокобезопасные коллекции: одна с данными прочитанными с диска, вторая со свободными блоками под эти самые данные. Получится красивая автоматическая синхронизация: воркеры не начнёт работу, пока в колеекции с данными нет этих данных, а поставщик не будет ничего читать с диска, пока воркеры не вернут хотя бы один блок в пул. Красота же.
0
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
17.01.2017, 06:38  [ТС] 11
TopLayer, ваше решение просто космос Я пока не разобрался как оно работает и как так быстро, но оно летает.

Добавлено через 8 минут
Usaga, я то не знаю на каких блоках оно будет тестироваться Постараюсь не использовать. На больших блоках не проверял, но на маленьких (10-1000 байт) получается что читающий поток наоборот ждет пока рабочие обработают блоки. В роли очереди использовал Queue. Постоянно возникает ощущение что я что-то делаю не так. Пока ковыряю примеры из вышеупомянутой темы.
Последнее сообщение не совсем понял.
0
Эксперт .NET
12073 / 8383 / 1280
Регистрация: 21.01.2016
Сообщений: 31,578
17.01.2017, 06:49 12
V0vchik, ты сам размер блока выбираешь. И зачем крайности такие? То 20Мб, то 1000 байт... Возьми пару мегабайт - этого должно быть более-менее достаточно.

Цитата Сообщение от V0vchik Посмотреть сообщение
В роли очереди использовал Queue
Есть специализированный для многопоточности класс коллекции о котором я выше упомянал - BlockCollection.

Цитата Сообщение от V0vchik Посмотреть сообщение
Последнее сообщение не совсем понял.
Ничего страшного. Со временем разберёшься. Но идея там проста: в программе есть только фиксированный набор блоков-контейнеров в которые данные с диска читаются. Эти блоки гоняются по кругу от читающего кода (Producer\Поставщик) к воркерам (Consumer\Потребитель) и назад к поставщику, когда работа над блоком окончена.

Такой подход позволяет вообще избавиться от сборки мусора как таковой в этом процессе, ведь блоки с буферами переиспользуются, а не создаются каждый раз при каждой итерации чтения.

И синхронизировать работу поставщика и воркеров проще: если все блоки заняты (пул пуст), то поставщик знает, что воркеры ещё не закончили работу и потому просто ждёт. Такую ситуацию легко можно будет увидеть в профилировщике и, соответственно, поигравщись размерами блоков\количеством потоков (воркеров) можно добиться оптимальной производительности.
0
12 / 12 / 13
Регистрация: 06.03.2011
Сообщений: 166
18.01.2017, 17:08  [ТС] 13
Usaga, здравствуйте. Сегодня перековырял все свои решения и понял что проблема изначально была вот в этом методе: PrintByteArray. Заменил его на: BitConverter.ToString(hash).Replace("-",""); и все полетело. На обработку 100к блоков раньше уходило больше двух минут, а сейчас 10 секунд. Не понимаю как я раньше не понял этого

Добавлено через 1 минуту
Usaga, а по поводу BlokingCollection я почитал о нем, но с ним не получиться. По условиям задания нельзя использовать ничего позже .net 3.5.
0
18.01.2017, 17:08
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
18.01.2017, 17:08
Помогаю со студенческими работами здесь

Последовательность байт файла, или определение сигнатуры в программе "Антивирус"
Сижу над курсовой &quot;антивирусник&quot;. Все прекрасно сделал: карантин, игнор все примочки. Единственный...

Генерация сигнатуры файла, файл делится на блоки равной длинны
Всем привет. Есть такое задание, и я понять не могу о чем идет речь. Сгенерировать сигнатуру файла....

Многопоточная генерация комбинаций
Здравствуйте. Хочу сделать генератор комбинация, вот код: import random...

Чтение полной сигнатуры файла - Си
У меня есть путь к файлу, необходимо прочесть сигнатуру этого файла по кускам, скажем по 32...

Написать консольную программу для генерации сигнатуры указанного файла (задействовать параллелизм)
Здравствуйте, дорогие форумчане. На днях подкинули заданице: Требуется написать консольную...

Генерация файла MP0B_001
Подскажите чем и как можно генерировать файл MP0B_001? Желательно на языке с#. Как генерировать...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru