我创建了以下程序来演示官方C#mongodb驱动程序中的并发更新问题。我的测试版本:1.9.0。如果我错过了什么,你能帮忙吗?我相信这是C#驱动程序中的一个错误。
说明:
- 程序在mytest数据库中删除并创建新页面集合。
- 使用增量ID填充1000页的页面集合。
- 为每个任务启动2个更新任务,一个前进和一个后向循环。
- 根据记录的数据,总共必须有1000个更新。
- 因为每次更新前都有时间条件检查。
- 测试显示大约有1000个更新。非确定性情况。
- 我可以理解大于1000的结果,但无法理解1000以下的结果,我可以看到3次执行中的1次。
醇>
任何帮助表示赞赏。感谢。
public class Document
{
public int _id { get; set; }
public long time { get; set; }
}
class Program
{
MongoClient Client;
MongoServer Server;
MongoDatabase DB;
MongoCollection<Document> Pages;
int NextId = 0;
DateTime TimeLimit;
int TotalUpdate = 0;
int totalPage = 1000;
private void Start()
{
Client = new MongoClient("mongodb://localhost:9147");
Server = Client.GetServer();
DB = Server.GetDatabase("mytest");
Pages = DB.GetCollection<Document>("pages");
Pages.Drop();
if (Pages.Count() > 0)
{
NextId = Pages.AsQueryable().Max(x => x._id);
}
else
{
Console.WriteLine(DateTime.Now + " Inserts started...");
for (int i = 0; i < totalPage; ++i)
{
Pages.Save(new Document
{
_id = Interlocked.Increment(ref NextId),
time = DateTime.Now.Ticks
});
}
Console.WriteLine(DateTime.Now + " Inserts finished...");
}
TimeLimit = DateTime.Now;
Thread.Sleep(500);
List<Task> tasks =new List<Task>();
tasks.Add(Task.Factory.StartNew(() => Updates()));
tasks.Add(Task.Factory.StartNew(() => Updates2()));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Total Update: " + TotalUpdate);
Console.WriteLine("Expected Total Update: " + totalPage);
}
private void Updates()
{
Console.WriteLine(DateTime.Now + " Updates 1 started...");
Parallel.ForEach(Enumerable.Range(1, totalPage), (id) =>
{
try
{
if (id % 100 == 0)
Console.WriteLine(DateTime.Now + " Update1: " + id);
var found = Pages.FindOne(Query.EQ("_id", id)).time;
if (found < TimeLimit.Ticks)
{
var res = Pages.Update(Query.EQ("_id", id), Update.Set("time", DateTime.Now.Ticks), UpdateFlags.Upsert, WriteConcern.Acknowledged);
if (res.DocumentsAffected > 0)
Interlocked.Increment(ref TotalUpdate);
else
Console.WriteLine(res.Response);
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
});
Console.WriteLine(DateTime.Now + " Updates 1 finished...");
}
private void Updates2()
{
Console.WriteLine(DateTime.Now + " Updates 2 started...");
Parallel.ForEach(Enumerable.Range(1, totalPage), (id) =>
{
try
{
if ((id - 1) % 100 == 0)
Console.WriteLine(DateTime.Now + " Update2: " + (totalPage - id + 1));
var found = Pages.FindOne(Query.EQ("_id", totalPage - id + 1)).time;
if (found < TimeLimit.Ticks)
{
var res = Pages.Update(Query.EQ("_id", totalPage - id + 1), Update.Set("time", DateTime.Now.Ticks), UpdateFlags.Multi, WriteConcern.Acknowledged);
if (res.DocumentsAffected > 0)
Interlocked.Increment(ref TotalUpdate);
else
Console.WriteLine(res.Response);
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
});
Console.WriteLine(DateTime.Now + " Updates 2 finished...");
}
static void Main(string[] args)
{
new Program().Start();
}
}
答案 0 :(得分:2)
这是因为DateTime.Now.Ticks
不是很精确,并且通常会返回相同的值。你看过你正在创建的数据了吗?许多time
值相等(取决于您的机器的速度)。
碰撞只能发生在两个行走方向相遇的中间。当你以同样的方式行走时,问题会严重恶化。
使用性能计数器获取高分辨率时间,改为使用计数器,或将比较从if (found < TimeLimit.Ticks)
更改为if (found <= TimeLimit.Ticks)
。后者会不时给你比预期更大的结果,但从不低于预期。
修改
也许我错过了一些东西,但我不确定你的测试能否提供可重现的结果。另一个线程完成后,检查if found < TimeLimit
将始终失败。但是,在另一个线程中,检查if found < currentTime()
(其中currentTime
是一个高性能计时器)实际上总是会产生true,即使其他线程刚刚在一微秒之前更新了文档。因此,测试应该产生大约1,500个更新,而不是1,000。粗糙度取决于计时器的精度和机器的性能。