如何通过线程或并行扩展提高 foreach 搜索的性能?

Posted

技术标签:

【中文标题】如何通过线程或并行扩展提高 foreach 搜索的性能?【英文标题】:How to increase performance of foreach search via threads or parallel extensions? 【发布时间】:2021-12-16 16:36:20 【问题描述】:

我对线程完全陌生。我有这样一种方法,我试图以线程安全的方式实现并行执行(至少我希望如此):

private void PerformSearch(List<FailedSearchReportModel> failedSearchReports)
    
        foreach (var item in failedSearchReports)
        
            item.SearchTerms = item.SearchTerms.Take(50).ToList();
            var siteId = ConstantsEnumerators.Constants.Projects.GetProjectIdByName(item.Site);
            if (SearchWrapperHelper.IsSeas(siteId))
            
                item.UsedEngine = "Seas";
                var model = GetBaseQueryModel(item.Site);
                Parallel.ForEach(item.SearchTerms,
                         new ParallelOptions  MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0)) ,
                         (term) =>
                     
                         lock (seasSyncRoot)
                         
                             CheckSearchTermInSeas(model, term, item.Site, item.Language);
                         
                     );
            
            else
            
                item.UsedEngine = "Fast";
                Parallel.ForEach(item.SearchTerms, term =>
                    
                        lock (fastSyncRoot)
                        
                            CheckSearchTermInFast(term, item.Site, item.Language);
                        
                    );
            
        
    

尽管在指南中提到 lock 语句只是为了包装尽可能少的代码,但嵌套的 CheckSearchTerm 方法如下所示:

private void CheckSearchTermInSeas(SearchQueryModel baseModel, FailedSearchTermModel term, string site, string language)
    
        var projectId = ConstantsEnumerators.Constants.Projects.GetProjectIdByName(site);

        term.SearchTerm = ClearSearchTerm(term.SearchTerm).Replace("\"", string.Empty);
        var results = SearchInSeas(baseModel, term.SearchTerm, projectId, language);
        term.DidYouMean = GetDidYouMean(results?.Query.Suggestion, term.SearchTerm);
        term.HasResult = results?.NumberOfResults > 0;
        if (!term.HasResult && string.IsNullOrEmpty(term.DidYouMean))
        
            results = SearchInSeas(baseModel, term.SearchTerm, projectId, InverseLanguage(language));
            term.WrongLanguage = results?.NumberOfResults > 0;
            if (!term.WrongLanguage)
            
                term.DidYouMean = GetDidYouMean(results?.Query.Suggestion, term.SearchTerm);
            
        

        if (!string.IsNullOrEmpty(term.DidYouMean))
        
            results = SearchInSeas(baseModel, term.DidYouMean, projectId, term.WrongLanguage ? InverseLanguage(language) : language);
            term.DidYouMeanHasResult = results?.NumberOfResults > 0;
            if (!term.DidYouMeanHasResult)
            
                results = SearchInSeas(baseModel, term.DidYouMean, projectId, term.WrongLanguage ? language : InverseLanguage(language));
                term.DidYouMeanHasResult = results?.NumberOfResults > 0;
            
        
    

我做的一切都正确吗,你能提供一些解释吗?还是我应该改变它? PS:现在如果我需要将所有这些记录写入文件(excel),我是否也应该使用 Parallel 来提高性能?如果是这样,方法是否相同?

【问题讨论】:

那是 C# 代码吗?我强烈建议您使用编写代码的语言标记问题。 据我所知,office 互操作库不是线程安全的 @TheGeneral,谢谢! 我想我们不需要添加锁,因为我们没有添加或修改公共变量/列表。如果您必须建议使用 ConcurrentDictionary 而不是 List。此外,如果只使用不可变对象,将私有函数更改为静态函数可以提高性能。 我建议检查纯方法和不可变类型。这些默认情况下是线程安全的。如果您有非纯方法或不知道,我建议您远离多线程,或者至少要非常小心。以这种方式围绕锁使用并行循环是一个糟糕的想法,但我们无法知道调用的方法是否是线程安全的。我建议从分析开始,看看多线程是否会有所帮助。 【参考方案1】:

在 ASP.NET 应用程序中,线程是一种宝贵的资源。您可用的线程越多,您可以同时处理的请求就越多。服务请求和并行工作的线程来自同一个池,ThreadPool。所以你做的并行工作越多,你可以服务的并发客户端就越少。当没有使用MaxDegreeOfParallelism 选项配置循环时,使用Parallel.ForEach 进行并行工作尤其令人讨厌。通过使用池中的每个可用线程并请求更多线程,这只野兽可以单枪匹马地saturate your ThreadPool。在您的 Web 应用程序中一个未配置的 Parallel.ForEach 足以将您的应用程序的可扩展性降低到虚无。

您的代码中的第二个Parallel.ForEach,即使用item.UsedEngine = "Fast" 设置搜索的那个,未配置。

所有这些线程要做什么?几乎没有。最多只有一两个线程在工作,其他线程都被阻塞在lock 后面,等待轮到它们。这不是利用服务器资源的有效方法。通过使用并行性,您让每个人的 Web 应用程序都变慢了。如果您在 Web 应用程序中遇到性能问题,那么引入并行性应该是您最后想到的解决方案。比解决问题更有可能加剧问题。

【讨论】:

以上是关于如何通过线程或并行扩展提高 foreach 搜索的性能?的主要内容,如果未能解决你的问题,请参考以下文章

通过将长时间运行的任务分成单独的进程来提高程序性能

通过将长时间运行的任务拆分为单独的进程来提高程序性能

如何提高报表的取数性能

如何通过 Parallel.ForEach 实现最大并行度并利用最大 CPU?

Parallel ForEach For 多线程并行计算使用注意

使用 R doParallel 或 foreach 从 mysql 并行获取数据