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



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

说明:

  1. 程序在mytest数据库中删除并创建新的页面集合
  2. 用1000个具有增量ID的页面填充页面集合
  3. 启动2个更新任务,每个任务一个正向循环,一个反向循环
  4. 根据记录的数据,总共必须有1000次更新
  5. 因为在每次更新之前都会进行时间条件检查
  6. 测试显示大约有1000次更新。不确定性情况
  7. 我可以理解大于1000的结果,但不能理解低于1000的结果——我可以看到三分之一的执行

感谢您的帮助。谢谢

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

之所以会发生这种情况,是因为DateTime.Now.Ticks不是很精确,并且通常会返回相同的值。你看了你正在创建的数据吗?许多time值是相等的(取决于机器的速度)。

碰撞只能发生在两个行走方向相交的中间位置。当你让他们以同样的方式行走时,问题会急剧恶化。

使用性能计数器获取高分辨率时间,或者使用计数器,或者将比较从if (found < TimeLimit.Ticks)更改为if (found <= TimeLimit.Ticks)。后者会不时给你带来比预期更大的结果,但永远不会低于预期。

编辑

也许我遗漏了什么,但我不确定你的测试是否能提供可重复的结果。一旦另一个线程完成,检查if found < TimeLimit将总是失败。然而,在另一个线程中,检查if found < currentTime()(其中currentTime是一个高性能计时器)实际上总是结果为true,即使另一个螺纹在一微秒之前刚刚更新了文档。因此,该测试应该产生大约1500个更新,而不是1000个。粗糙度取决于计时器的精度和机器的性能。

最新更新