如何通过线程或并行扩展提高 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?