在我的下面的代碼中,我鎖定了guid,嘗試使其線程安全。 用我的示例應用程序,我會每運行10次程序就會得到一個「重複密鑰」。又名,我得到一份重複,這不是我所需要的。構建一個線程安全的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();
}
}
}
我的代碼的組合:
因爲我得到 「爲什麼不使用Guid.NewGuid」 的問題,我將提供原因在這裏:
我有一個父進程,有一個唯一的事由Guid.NewGuid()創建的ifier。我將這稱爲「父母指導」。該父進程將創建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」代表......然後我得到「遞增」guid的結果。
還有。我可以編寫更好的單元測試,因爲現在我會提前知道文件名。
附加:
最終代碼版本:
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;
}
}
那豈不是更容易得到一個隨機GUID而不是在增加?鑑於你計劃使用多線程的事實,有兩個線程生成相同的隨機引導的可能性比兩個線程從1遞增到2的可能性要小。 – Lidaranis
有沒有原因'return'在'lock'之外?看起來像是其中一個線程在返回之前暫停,並且下一個線程被允許進入關鍵部分,後者修改該變量並返回相同的值。 –
你爲什麼不使用Guid.NewGuid()?使用.NET給你的工具。 –