【问题标题】:Parallel hash computing via multiple TransformBlocks results in a disarray通过多个 TransformBlock 进行并行哈希计算导致混乱
【发布时间】:2015-10-18 09:13:03
【问题描述】:

我正在尝试计算整个目录的哈希值,以便稍后监控更改。这相对容易。但是,如果有大文件,计算需要太多时间,所以我最终使用了一些多线程。

由于 I/O 瓶颈,我应该用一个线程读取一个文件,但我可以通过一次调用 TransformBlock 方法在多个线程中计算该文件的哈希值。问题是,每次计算的结果都是不同的——因为所有线程都会更新一个 hashAlgorithm 的实例,它们会不规律地进行。

  public delegate void CalculateHashDelegate(byte[] buffer);
  private MD5 md5;        
  private long completed_threads_hash;
  private object lock_for_hash = new object();

 `private string getMd5Hash(string file_path)
  {
        string file_to_be_hashed = file_path;
        byte[] hash;

        try
        {
            CalculateHashDelegate CalculateHash = AsyncCalculateHash;
            md5 = MD5.Create();

            using (Stream input = File.OpenRead(file_to_be_hashed))
            {
                int buffer_size = 0x4096;
                byte[] buffer = new byte[buffer_size];

                long part_count = 0;
                completed_threads_hash = 0;
                int bytes_read;
                while ((bytes_read = input.Read(buffer, 0, buffer.Length)) == buffer_size)
                {
                    part_count++;
                    IAsyncResult ar_hash = CalculateHash.BeginInvoke(buffer, CalculateHashCallback, CalculateHash);
                }

                // Wait for completing all the threads
                while (true)
                {
                    lock (completed_threads_lock)
                    {
                        if (completed_threads_hash == part_count)
                        {  
                            md5.TransformFinalBlock(buffer, 0, bytes_read);
                            break;
                        }
                    }
                }

                hash = md5.Hash;

            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            md5.Clear();
            return sb.ToString();
        }
        catch (Exception ex)
        {
            Console.WriteLine("An exception was encountered during hashing file {0}. {1}.", file_to_be_hashed, ex.Message);
            return ex.Message;
        }
    }

    public void AsyncCalculateHash(byte[] buffer)
    {
        lock (lock_for_hash)
        {
            md5.TransformBlock(buffer, 0, buffer.Length, null, 0);
        }
    }

    private void CalculateHashCallback(IAsyncResult ar_hash)
    {
        try
        {
            CalculateHashDelegate CalculateHash = ar_hash.AsyncState as CalculateHashDelegate;
            CalculateHash.EndInvoke(ar_hash);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Callback exception: ", ex.Message);
        }
        finally
        {
            lock (completed_threads_lock)
            {
                completed_threads_hash++;
            }
        }
    }

有没有办法组织散列过程?我不能使用比 3.5 更新的 .Net 以及诸如 BackroundWorker 和 ThreadPool 之类的类。或者也许还有另一种并行哈希计算的方法?

【问题讨论】:

    标签: c# multithreading hash


    【解决方案1】:

    通常,您不能在多线程代码中使用加密对象。散列方法的问题在于它们是完全线性的——每个散列块取决于当前状态,并且状态是使用所有先前的块计算的。所以基本上,你不能对 MD5 这样做。

    还有另一个进程可以使用,它被称为哈希树或Merkle tree。基本上,您决定块大小并计算块的哈希值。这些散列被放在一起并再次散列。如果您有大量散列,您实际上可以创建一个树,如前面链接到的 Wikipedia 文章中所述。当然,生成的哈希与 MD5 不同,取决于哈希树的配置参数。

    请注意,MD5 已损坏。您应该改用 SHA-256 或 SHA-512/xxx(在 64 位处理器上更快)。另请注意,通常 IO 速度比散列算法的速度更受阻碍,从而抵消了散列树的任何速度优势。如果您有很多文件,您还可以在文件级别并行化哈希。

    【讨论】:

    • 我可以使用任何 HashAlgorithm 的子类,为简单起见选择了 MD5。而且我不能并行从硬盘读取,因为在大文件的情况下会导致不可预测的延迟。如果有什么并行化的方法,应该在哈希计算部分找到。
    • 我已经尝试实现连接散列的散列,看起来很有希望,但是将所有块的散列连接到一个字节 [] 数组中,显示不同的结果。当我以线性方法进行时,结果很好,恒定。但是当我在并行线程中执行此操作时,结果几乎每隔一次都不同。我认为,这是因为与 TransformBlocks 相同的问题 - 产生散列的源是可变的。而且我看不到如何在并行线程中创建一个实际的 Merkle 树 - 中间散列也会有所不同。我错了吗?
    • 只要保持哈希的顺序,就应该始终创建相同的 Merkle 树哈希结果。显然,您需要保持块大小等不变;树本身每次看起来都应该相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 2011-10-24
    • 2014-08-28
    • 2012-10-26
    • 2021-12-24
    相关资源
    最近更新 更多