多线程程序中的竞争条件:id计数器有时不会增加

时间:2019-08-20 10:46:33

标签: c# wpf multithreading race-condition

我目前正在从事一个个人项目,旨在管理文件集合。文件被分类到库中并存储在文件夹中。应用启动时,我想将所有文件加载到内存中并为其分配唯一的ID。由于id仅在运行时很重要,因此我使用一个简单的整数,该整数在加载新文件时会递增。

文件夹结构如下:

- AppFolder
    - LibraryFolder1
        - FileFolder11
            - File111
            - File112
        - FileFolder12
            - File121
            - File122
                 .
                 .
                 .
    - LibraryFolder2
        - FileFolder21
                 .
                 .
                 .

为了增加启动时间,我使用了多线程方法。每个线程都被分配了库文件夹的一部分,并从那里启动了其他线程,这些线程在FileFolders的一部分上工作。 通过这种方法,我获得了显着的性能提升,因此我希望朝这个方向继续。

不幸的是,该代码似乎有一个错误,因为在某些运行中,将id分配了两次,因此我在以id为键将它们添加到Dictionary时遇到了错误。

通过调用LoadLibraries()方法开始加载过程。

public class LibraryManager
{
    private IDictionary<int, Library> mLibraries;
    public IDictionary<int, Library> Libraries
    {
        get { return mLibraries; }
        private set { mLibraries = value; }
    }

    private int IDCount = 0;

    private static LibraryManager mInstance;

    public static LibraryManager Instance
    {
        get
        {
            if (mInstance == null)
            {
                mInstance = new LibraryManager();
            }
            return mInstance;
        }
    }


    private LibraryManager()
    {
        Libraries = new ConcurrentDictionary<int, Library>();
    }

    /// <summary>
    /// Creates a new Library id. This method uses an auto increment method.
    /// </summary>
    /// <returns>unique runtime id for a library</returns>
    private int GetNewID()
    {
        return Interlocked.Increment(ref IDCount);
    }

    /// <summary>
    /// Reads all libraries in the libraries folder and creates 
    /// corresponding Library objects.
    /// Library folders must contain a library.nfo file.
    /// </summary>
    public void LoadLibraries(string libraryFolderPath)
    {
        Libraries.Clear();

        string[] libraryFolders = Directory.GetDirectories(libraryFolderPath);
        int threadCount = Math.Max(1, Math.Min(libraryFolders.Length / 25, Environment.ProcessorCount));
        int batchSize = (int)Math.Ceiling((double)libraryFolders.Length / threadCount);

        for (int i = 0; i < threadCount; i++)
        {
            int start = batchSize * i;
            int end = Math.Min((i + 1) * batchSize, libraryFolders.Length);

            Thread thread = new Thread(() => LoadLibraries(libraryFolders, start, end));
            thread.IsBackground = true;
            thread.Start();
        }
    }

    /// <summary>
    /// Loads a section of the library folders from the app folder.
    /// </summary>
    /// <param name="libraryFolders">list of paths to library folders</param>
    /// <param name="start">start index</param>
    /// <param name="end">end index</param>
    private void LoadLibraries(string[] libraryFolders, int start, int end)
    {
        for (int i = start; i < end; i++)
        {
            Library library = new Library(GetNewID());

            library.LoadFiles(libraryFolders[i]);

            Libraries.Add(library.ID, library);
        }
    }
}
public class Library
    {

        private IDictionary<int, FileObject> mFileObjects;

        public IDictionary<int, FileObject> FileObjects
        {
            get { return mFileObjects; }
            private set { mFileObjects = value; }
        }

        private int mID;

        public int ID
        {
            get { return mID; }
            private set { mID = value; }
        }


        public Library(int id)
        {
            ID = id;
            FileObjects = new ConcurrentDictionary<int, FileObject>();
        }

        /// <summary>
        /// Checks whether this library contains a fileObject with the same id.
        /// </summary>
        /// <param name="id">id which is searched for</param>
        /// <returns>true if a fileObject with the same id is already added to this library</returns>
        public bool Contains(int id)
        {
            return FileObjects.ContainsKey(id);
        }

        /// <summary>
        /// Checks whether this library contains the given file object.
        /// </summary>
        /// <param name="fileObject">fileObject which is searched for</param>
        /// <returns>true if the fileobject is already in the library</returns>
        public bool Contains(FileObject fileObject)
        {
            return Contains(fileObject.ID);
        }

        /// <summary>
        /// Loads all files currently created for this library.
        /// </summary>
        public void LoadFiles(string libraryFolder)
        {
            string[] fileFolders = Directory.GetDirectories(libraryFolder);
            int threadCount = Math.Max(1, Math.Min(fileFolders.Length / 2, Environment.ProcessorCount));
            int batchSize = (int)Math.Ceiling((double)fileFolders.Length / threadCount);

            for (int i = 0; i < threadCount; i++)
            {
                int start = batchSize * i;
                int end = Math.Min((i + 1) * batchSize, fileFolders.Length);

                Thread thread = new Thread(() => LoadFiles(fileFolders, start, end));
                thread.IsBackground = true;
                thread.Start();
            }
        }

        /// <summary>
        /// Loads a section of the file folders from the FileFolder.
        /// </summary>
        /// <param name="fileFolders ">list of all file folders</param>
        /// <param name="start">start index</param>
        /// <param name="end">end index</param>
        private void LoadFiles(string[] fileFolders, int start, int end)
        {
            for (int i = start; i < end; i++)
            {
                FileObject fileObject = FileManager.Instance.CreateFileObject();

                AddFileObject(fileObject);
            }
        }

        /// <summary>
        /// Adds a fileObject to this library if it is not already added.
        /// </summary>
        /// <param name="fileObject">file object to add</param>
        private void AddFileObject(FileObject fileObject)
        {
            if (!(fileObject == null || Contains(fileObject))) // (1)
            {
                fileObject.Library = this;

                FileObjects.Add(fileObject.ID, fileObject); // (2)
            }
        }

    }
public class FileManager
    {

        private int IDCount = 0;

        private static FileManager mInstance;

        public static FileManager Instance
        {
            get
            {
                if (mInstance == null)
                {
                    mInstance = new FileManager();
                }

                return mInstance;
            }
        }


        private FileManager()
        {

        }

        /// <summary>
        /// Creates a new FileObject id. This method uses an auto increment method.
        /// </summary>
        /// <returns>unique runtime id for a file object</returns>
        private int GetNewID()
        {
            return Interlocked.Increment(ref IDCount);
        }

        public FileObject CreateFileObject()
        {
            return new FileObject(GetNewID());
        }

    }
public class FileObject
    {

        private int mID;

        public int ID
        {
            get { return mID; }
            private set { mID = value; }
        }

        private Library mLibrary;

        public Library Library
        {
            get { return mLibrary; }
            set { mLibrary = value; }
        }

        public FileObject(int id)
        {
            ID = id;
        }

    }
class Program
    {
        static void Main(string[] args)
        {
            LibraryManager.Instance.LoadLibraries(Path to AppFolder);

            Console.ReadLine(); // wait for threads to run
        }
    }

据我所知,代码中的标记部分(// (1)// (2))发生了错误。 Contains(FileObject f)失败是因为已经添加了另一个具有相同ID的对象,或者词典引发了重复的输入异常。 看来这是一种竞争状况,但我不知道如何解决。

如果有人能分享一些见解,我将不胜感激。

0 个答案:

没有答案