Эффективный подход к многопоточной разности множеств - PullRequest
2 голосов
/ 12 марта 2012

У меня есть конечный набор потребительских потоков, каждый из которых потребляет работу.После обработки задания у них появляется список подзадач, которые были перечислены в использованном задании.Мне нужно добавить подзадачи из этого списка, которых у меня еще нет в базе данных.В базе данных 3 миллиона, поэтому получение списка, которых еще нет в базе данных, идет медленно.Я не возражаю против того, чтобы каждый поток блокировал этот вызов, но, поскольку у меня есть условие гонки (см. Код), я должен заблокировать их все при этом медленном вызове, чтобы они могли вызывать только этот раздел по одному за раз, и моя программа сканирует.Что я могу сделать, чтобы исправить это, чтобы потоки не замедлялись для этого вызова?Я пробовал очередь, но поскольку потоки выталкивают списки заданий быстрее, чем компьютер может определить, какие из них следует добавить в базу данных, я получаю очередь, которая продолжает расти и никогда не очищается.

Myкод:

IEnumerable<string> getUniqueJobNames(IEnumerable<job> subJobs, int setID)
{
    return subJobs.Select(el => el.name)
        .Except(db.jobs.Where(el => el.set_ID==setID).Select(el => el.name));
}

//...consumer thread i
lock(lockObj)
{
    var uniqueJobNames = getUniqueJobNames(consumedJob.subJobs, consumerSetID);
    //if there was a context switch here to some thread i+1
    //   and that thread found uniqueJobs that also were found in thread i
    //   then there will be multiple copies of the same job added in the database.
    //   So I put this section in a lock to prevent that.
    saveJobsToDatabase(uniqueJobName, consumerSetID);
}
//continue consumer thread i...

1 Ответ

2 голосов
/ 12 марта 2012

Вместо того, чтобы возвращаться в базу данных для проверки уникальности имен заданий, вы можете внести соответствующую информацию в поисковую структуру данных в памяти, что позволяет гораздо быстрее проверять существование:

Dictionary<int, HashSet<string>> jobLookup = db.jobs.GroupBy(i => i.set_ID)
    .ToDictionary(i => i.Key, i => new HashSet<string>(i.Select(i => i.Name)));

Это вы делаете только один раз. После этого каждый раз, когда вам нужно проверить уникальность, вы используете поиск:

IEnumerable<string> getUniqueJobNames(IEnumerable<job> subJobs, int setID)
{
    var existingJobs = jobLookup.ContainsKey(setID) ? jobLookup[setID] : new HashSet<string>();

    return subJobs.Select(el => el.Name)
        .Except(existingJobs);
}

Если вам нужно ввести новое вспомогательное задание, добавьте его в поиск:

lock(lockObj)
{
    var uniqueJobNames = getUniqueJobNames(consumedJob.subJobs, consumerSetID);
    //if there was a context switch here to some thread i+1
    //   and that thread found uniqueJobs that also were found in thread i
    //   then there will be multiple copies of the same job added in the database.
    //   So I put this section in a lock to prevent that.
    saveJobsToDatabase(uniqueJobName, consumerSetID);

    if(!jobLookup.ContainsKey(newconsumerSetID))
    {
        jobLookup.Add(newconsumerSetID, new HashSet<string>(uniqueJobNames));
    }
    else
    {
        jobLookup[newconsumerSetID] = new HashSet<string>(jobLookup[newconsumerSetID].Concat(uniqueJobNames)));
    }
}
...