C#MongoDB驱动程序并发更新问题(带测试代码)

时间:2014-04-22 21:02:06

标签: c# mongodb concurrency mongodb-.net-driver insert-update

我创建了以下程序来演示官方C#mongodb驱动程序中的并发更新问题。我的测试版本:1.9.0。如果我错过了什么,你能帮忙吗?我相信这是C#驱动程序中的一个错误。

说明:

  
      
  1. 程序在mytest数据库中删除并创建新页面集合。
  2.   
  3. 使用增量ID填充1000页的页面集合。
  4.   
  5. 为每个任务启动2个更新任务,一个前进和一个后向循环。
  6.   
  7. 根据记录的数据,总共必须有1000个更新。
  8.   
  9. 因为每次更新前都有时间条件检查。
  10.   
  11. 测试显示大约有1000个更新。非确定性情况。
  12.   
  13. 我可以理解大于1000的结果,但无法理解1000以下的结果,我可以看到3次执行中的1次。
  14.   

任何帮助表示赞赏。感谢。

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();
    }
}

1 个答案:

答案 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。粗糙度取决于计时器的精度和机器的性能。