c# – 构建一个线程安全的GUID增量器

在我的下面的代码中,我正在锁定guid,尝试使其线程安全.
使用我的示例应用程序,每运行一次程序,我将获得一个“重复密钥”.阿卡,我得到了副本,这不是我需要的.

反正是否使“.NextGuid”线程安全?

using System;    
namespace MyConsoleOne.BAL
{
    public class GuidStore
    {
        private static object objectlock = new object();    
        private Guid StartingGuid { get; set; }    
        private Guid? LastGuidHolder { get; set; }    
        public GuidStore(Guid startingGuid)
        {
            this.StartingGuid = startingGuid;
        }

        public Guid? GetNextGuid()
        {
            lock (objectlock)
            {
                if (this.LastGuidHolder.HasValue)
                {
                    this.LastGuidHolder = Increment(this.LastGuidHolder.Value);
                }
                else
                {
                    this.LastGuidHolder = Increment(this.StartingGuid);
                }
            }    
            return this.LastGuidHolder;
        }

        private Guid Increment(Guid guid)
        {    
            byte[] bytes = guid.ToByteArray();    
            byte[] order = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };    
            for (int i = 0; i < 16; i++)
            {
                if (bytes[order[i]] == byte.MaxValue)
                {
                    bytes[order[i]] = 0;
                }
                else
                {
                    bytes[order[i]]++;
                    return new Guid(bytes);
                }
            }    
            throw new OverflowException("Guid.Increment failed.");
        }
    }
}

using MyConsoleOne.BAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyConsoleOne
{
    class Program
    {
        static void Main(string[] args)
        {
            GuidStore gs = new GuidStore(Guid.NewGuid());

            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(i);
                Dictionary<Guid, int> guids = new Dictionary<Guid, int>();
                Parallel.For(0, 1000, j =>
                {
                    Guid? currentGuid = gs.GetNextGuid();
                    guids.Add(currentGuid.Value, j);
                    Console.WriteLine(currentGuid);
                }); // Parallel.For
            }    
            Console.WriteLine("Press ENTER to Exit");
            Console.ReadLine();
        }
    }
}

我的代码是以下组合:

> Making Guid properties threadsafe
> Increment Guid in C#

由于我收到“为什么不使用Guid.NewGuid”问题,我将在此提供原因:

我有一个父进程,它有一个由Guid.NewGuid()创建的uniqueidentifier.我将其称为“父母指南”.该父进程将创建N个文件.如果我是从头开始写的,我只是在文件名的末尾附加“N”.因此,如果父Guid是“11111111-1111-1111-1111-111111111111”,我会写文件

"11111111-1111-1111-1111-111111111111_1.txt"
"11111111-1111-1111-1111-111111111111_2.txt"
"11111111-1111-1111-1111-111111111111_3.txt"

但是,通过现有的“合同”与客户端:::文件名必须有一个(唯一的)Guid,文件名中没有“N”(1,2,等等)值(这个“合同”已经存在多年了,所以它的确如此相似).使用此处列出的功能,我将能够保留“合同”,但文件名与“父”Guid(父级由Guid.NewGuid()再次生成)松散关联.碰撞不是文件名的问题(它们被放在“进程”执行的不同文件夹下).碰撞是“父母”Guid的问题.但同样,这已经用Guid.NewGuid处理了.

因此,使用“11111111-1111-1111-1111-111111111111”的起始Guid,我将能够编写如下文件名:

OTHERSTUFF_111111111-1111-1111-1111-111111111112_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111113_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111114_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111115_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111116_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111117_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111118_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-111111111119_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-11111111111a_MORESTUFF.txt
OTHERSTUFF_111111111-1111-1111-1111-11111111111b_MORESTUFF.txt

所以在我上面的例子中,“父guid”由“this.StartingGuid”表示….然后我得到“增加”指导.

并且.我可以编写更好的单元测试,因为现在我会提前知道文件名.

附加:

最终代码版本:

public class GuidStore
{
    private static object objectlock = new object();

    private static int[] byteOrder = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

    private Guid StartingGuid { get; set; }

    private Guid? LastGuidHolder { get; set; }

    public GuidStore(Guid startingGuid)
    {
        this.StartingGuid = startingGuid;
    }

    public Guid GetNextGuid()
    {
        return this.GetNextGuid(0);
    }

    public Guid GetNextGuid(int firstGuidOffSet)
    {
        lock (objectlock)
        {
            if (this.LastGuidHolder.HasValue)
            {
                this.LastGuidHolder = Increment(this.LastGuidHolder.Value);
            }
            else
            {
                this.LastGuidHolder = Increment(this.StartingGuid);
                for (int i = 0; i < firstGuidOffSet; i++)
                {
                    this.LastGuidHolder = Increment(this.LastGuidHolder.Value);
                }
            }

            return this.LastGuidHolder.Value;
        }
    }

    private static Guid Increment(Guid guid)
    {
        var bytes = guid.ToByteArray();
        var canIncrement = byteOrder.Any(i => ++bytes[i] != 0);
        return new Guid(canIncrement ? bytes : new byte[16]);
    }
}

和单元测试:

public class GuidStoreUnitTests
{
    [TestMethod]
    public void GetNextGuidSimpleTest()
    {
        Guid startingGuid = new Guid("11111111-1111-1111-1111-111111111111");
        GuidStore gs = new GuidStore(startingGuid);


        List<Guid> guids = new List<Guid>();

        const int GuidCount = 10;

        for (int i = 0; i < GuidCount; i++)
        {
            guids.Add(gs.GetNextGuid());
        }

        Assert.IsNotNull(guids);
        Assert.AreEqual(GuidCount, guids.Count);
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111112")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111113")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111114")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111115")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111116")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111117")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111118")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-111111111119")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-11111111111a")));
        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-11111111111b")));
    }

    [TestMethod]
    public void GetNextGuidWithOffsetSimpleTest()
    {
        Guid startingGuid = new Guid("11111111-1111-1111-1111-111111111111");
        GuidStore gs = new GuidStore(startingGuid);

        List<Guid> guids = new List<Guid>();

        const int OffSet = 10;

        guids.Add(gs.GetNextGuid(OffSet));

        Assert.IsNotNull(guids);
        Assert.AreEqual(1, guids.Count);

        Assert.IsNotNull(guids.FirstOrDefault(g => g == new Guid("11111111-1111-1111-1111-11111111111c")));
    }

    [TestMethod]
    public void GetNextGuidMaxRolloverTest()
    {
        Guid startingGuid = new Guid("ffffffff-ffff-ffff-ffff-ffffffffffff");
        GuidStore gs = new GuidStore(startingGuid);

        List<Guid> guids = new List<Guid>();

        const int OffSet = 10;

        guids.Add(gs.GetNextGuid(OffSet));

        Assert.IsNotNull(guids);
        Assert.AreEqual(1, guids.Count);

        Assert.IsNotNull(guids.FirstOrDefault(g => g == Guid.Empty));
    }

    [TestMethod]
    public void GetNextGuidThreadSafeTest()
    {
        Guid startingGuid = Guid.NewGuid();
        GuidStore gs = new GuidStore(startingGuid);

        /* The "key" of the ConcurrentDictionary must be unique, so this will catch any duplicates */
        ConcurrentDictionary<Guid, int> guids = new ConcurrentDictionary<Guid, int>();
        Parallel.For(
            0,
            1000,
            j =>
            {
                Guid currentGuid = gs.GetNextGuid();
                if (!guids.TryAdd(currentGuid, j))
                {
                    throw new ArgumentOutOfRangeException("GuidStore.GetNextGuid ThreadSafe Test Failed");
                }
            }); // Parallel.For
    }

    [TestMethod]
    public void GetNextGuidTwoRunsProduceSameResultsTest()
    {
        Guid startingGuid = Guid.NewGuid();

        GuidStore gsOne = new GuidStore(startingGuid);

        /* The "key" of the ConcurrentDictionary must be unique, so this will catch any duplicates */
        ConcurrentDictionary<Guid, int> setOneGuids = new ConcurrentDictionary<Guid, int>();
        Parallel.For(
            0,
            1000,
            j =>
            {
                Guid currentGuid = gsOne.GetNextGuid();
                if (!setOneGuids.TryAdd(currentGuid, j))
                {
                    throw new ArgumentOutOfRangeException("GuidStore.GetNextGuid ThreadSafe Test Failed");
                }
            }); // Parallel.For

        gsOne = null;

        GuidStore gsTwo = new GuidStore(startingGuid);

        /* The "key" of the ConcurrentDictionary must be unique, so this will catch any duplicates */
        ConcurrentDictionary<Guid, int> setTwoGuids = new ConcurrentDictionary<Guid, int>();
        Parallel.For(
                0,
                1000,
                j =>
                {
                    Guid currentGuid = gsTwo.GetNextGuid();
                    if (!setTwoGuids.TryAdd(currentGuid, j))
                    {
                        throw new ArgumentOutOfRangeException("GuidStore.GetNextGuid ThreadSafe Test Failed");
                    }
                }); // Parallel.For

        bool equal = setOneGuids.Select(g => g.Key).OrderBy(i => i).SequenceEqual(
                         setTwoGuids.Select(g => g.Key).OrderBy(i => i), new GuidComparer<Guid>());

        Assert.IsTrue(equal);
    }
}

internal class GuidComparer<Guid> : IEqualityComparer<Guid>
{
    public bool Equals(Guid x, Guid y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(Guid obj)
    {
        return 0;
    }
}

最佳答案:

你有两个问题:

> Dictionary.Add()不是线程安全的.请改用ConcurrentDictionary.TryAdd().
>你的GetNextGuid()实现有一个竞争条件,因为你正在锁定之外返回this.LastGuidHolder,因此它可以在返回之前被另一个线程修改.

一个明显的解决方案是在锁内移动返回:

public Guid? GetNextGuid()
{
    lock (objectlock)
    {
        if (this.LastGuidHolder.HasValue)
        {
            this.LastGuidHolder = Increment(this.LastGuidHolder.Value);
        }
        else
        {
            this.LastGuidHolder = Increment(this.StartingGuid);
        }

        return this.LastGuidHolder;
    }
}

但是,我会将返回类型更改为Guid – 它似乎没有用于返回Guid的任何目的? – 这应该隐藏在课堂内:

public Guid GetNextGuid()
{
    lock (objectlock)
    {
        if (this.LastGuidHolder.HasValue)
        {
            this.LastGuidHolder = Increment(this.LastGuidHolder.Value);
        }
        else
        {
            this.LastGuidHolder = Increment(this.StartingGuid);
        }

        return this.LastGuidHolder.Value;
    }
}

这是使用ConcurrentDictionary的测试方法的一个版本:

static void Main(string[] args)
{
    GuidStore gs = new GuidStore(Guid.NewGuid());

    for (int i = 0; i < 1000; i++)
    {
        Console.WriteLine(i);
        ConcurrentDictionary<Guid, int> guids = new ConcurrentDictionary<Guid, int>();
        Parallel.For(0, 1000, j =>
        {
            Guid currentGuid = gs.GetNextGuid();
            if (!guids.TryAdd(currentGuid, j))
            {
                Console.WriteLine("Duplicate found!");
            }
        }); // Parallel.For
    }

    Console.WriteLine("Press ENTER to Exit");
    Console.ReadLine();
}

说了这么多,我无法理解你为什么不只是使用Guid.NewGuid()……

上一篇:oracle生成主键唯一的id,函数SYS_GUID()


下一篇:系统重装之认识UEFI