65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的多线程搜索引擎

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.31/5 (9投票s)

2006年4月29日

24分钟阅读

viewsIcon

63368

许多应用程序都需要某种搜索功能。有时,我们正在寻找的信息位于许多不同的位置。

摘要
许多应用程序都需要某种搜索功能。有时,我们正在寻找的信息位于许多不同的位置。大多数情况下,即使资源彼此独立且物理分离,我们仍然必须按照代码中定义的顺序逐个执行搜索。在本文中,我们将介绍 .NET Framework 中 System.Threading 命名空间提供的简单但复杂的多线程模型,通过该模型,我们可以简单地为每个搜索创建一个线程,同时启动它们,然后让它们并行运行。当搜索完成后,我们可以处理它们返回的结果。
文章

 

许多应用程序都需要某种搜索功能。要搜索的信息通常位于许多不同的位置。我们构建的大多数应用程序可能只从一个位置(如数据库服务器)进行搜索,但有时我们需要从多个位置(如数据库服务器或文件服务器)搜索信息。然而,大多数情况下,即使资源彼此独立且物理分离,我们仍然必须按照代码中定义的顺序逐个执行搜索。在 Windows 中,这仅仅是因为我们开发应用程序所使用的语言要么缺乏这种能力,要么很难让我们同时启动所有搜索并并行运行。借助 .NET Framework 中 System.Threading 命名空间提供的简单而复杂的多线程模型,这现在是一个相对容易的任务。我们可以简单地为每个搜索创建一个线程,同时启动它们,然后让它们并行运行。当搜索完成后,我们可以处理它们返回的结果。多线程编程是一个庞大的主题,本文无法完全涵盖。本文假定您对该概念有基本的了解(您可以从文章末尾的链接中了解更多信息)。我们将在本文中重点介绍如何以非常实用的方式应用它。

搜索引擎

在本文中,我们将使用一个小型搜索引擎,它从三个独立的位置搜索信息:数据库服务器、目录服务器和互联网。这是一个基于网络的搜索引擎,结果显示在网页上。我们将首先了解一个不带线程的标准搜索引擎。然后使用线程。最后,我们将介绍带有线程和超时的搜索引擎。本文的主要目的只是演示如何在搜索中实现线程。我们将只使用一些静态值作为搜索结果,并使用 Thread.Sleep() 方法模拟实际搜索通常会花费的时间。我们将为搜索互联网设置五秒,为数据库服务器设置三秒,为目录服务器设置一秒。

此示例搜索引擎已在 Microsoft .NET SDK beta 2.0 中开发和测试。所有源代码都可以在支持代码下载中找到,以及一些用于编译它们的批处理文件。它包含 7 个文件。

  • bin\link.cs,一个链接的类定义。
  • bin\result.cs,搜索结果的类定义。
  • bin\linearSearch.cs,线性搜索引擎的类定义。
  • bin\parallelSearch.cs,并行搜索引擎的类定义。
  • search1.aspx,调用 linearSearch 类中搜索方法的 ASP 代码。
  • search2.aspx,调用 parallelSearch 类中搜索方法的 ASP 代码。
  • search3.aspx,调用 parallelSearch 类中带超时的搜索方法的 ASP 代码。

按顺序搜索的搜索引擎

在这里,我们有一个 C# 版本的搜索,类似于我们通常在应用程序中看到的。

link.cs

using System;

public class link {

private string _name = "";
private string _url = "";

public string name {
get { return _name; }
}

public string url {
get { return _url; }
}

public link(string pName, string pUrl) {
_name = pName;
_url = pUrl;
}
}

没什么特别的——只是一个保存链接信息(即 URL 和名称)的类。

result.cs


using System;
using System.Collections; // for ArrayList

public class result {

private string _label = "";
private string _message = "";
private ArrayList _links = new ArrayList();
private bool _isCompleted = false;
private DateTime _started;
private DateTime _done;

public string label {
get { return _label; }
}

public string message {
get { return _message; }
set { _message = value; }
}

public ArrayList links {
get { return _links; }
}

public bool isCompleted {
get { return _isCompleted; }
set {
_isCompleted = value;
_done = DateTime.Now;
}
}

public TimeSpan timeSpent {
get { return _done.Subtract( _started ); }
}

public result(string pLabel) {
_started = DateTime.Now;
_label = pLabel;
}
}

这里有更多的代码,但仍然相当直观。links 包含一个 link 对象的 ArrayList。timeSpent 是一个 TimeSpan 对象。它在 result 对象创建时开始,并在 isComplete 设置时结束。

linearSearch.cs

using System;
using System.Threading; // for Threading
using System.Collections; // for ArrayList

public class linearSearch {

private ArrayList _results = null;
private string _keyword;
private DateTime _started;
private DateTime _end;

private void addResult(result thisResult) {
_results.Add( thisResult );
}

public TimeSpan totalTime {
get { return _end.Subtract( _started ); }
}

public linearSearch() {}

public ArrayList search(string keyword) {

if ( keyword == null || keyword == "" )
return null;

_started = DateTime.Now;
_keyword = keyword;
_results = new ArrayList();

searchWebSites();
searchDatabases();
searchDirectoryServers();

_end = DateTime.Now;
return _results;
}

private void searchDatabases() {

result r = new result("数据库服务器中“" + _keyword + "”的结果");
try {
// 在实际场景中,您会希望连接到一些数据库,
// 并执行一些真实的搜索。但在这里我们只使用一些静态值,
// 并使用 Thread.Sleep() 模拟一些延迟,使其看起来像是
// 实际上正在做一些耗时的工作。

// 从公司的图书库存数据库中找到 3 条记录
// 模拟 3 秒的数据库搜索
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《锡公主》", "/") );
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《奥兹国的锡木匠》", "/") );
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《锡人》", "/") );
r.message = "找到: " + r.links.Count;
r.isCompleted = true;

} catch(Exception e) {

r.isCompleted = false;
r.message = "失败";

} finally {
// 释放数据库连接资源
// 然后添加结果
// 需要部分/全部结果
addResult(r);
}
}

private void searchDirectoryServers() {

result r = new result("目录服务器中“" + _keyword + "”的结果");
try {
// 从公司员工目录中找到 2 条记录
// 模拟一秒钟的搜索。
Thread.Sleep(1000);
// "Christine Yakukilu" 在我们的搜索结果中只是因为
// "Christine" 中有一个 "tin"
r.links.Add( new link("员工 - Christine Yakukilu", "/") );
r.links.Add( new link("员工 - Tin Lam", "/") );
r.message = "找到: " + r.links.Count;
r.isCompleted = true;

} catch(Exception e) {

r.isCompleted = false;
r.message = "失败";

} finally {
// 释放目录服务器连接资源
// 然后添加结果
addResult(r);
}
}

private void searchWebSites() {

result r = new result("网站中“" + _keyword + "”的结果");
try {
// 从网络上找到 4 个网站
// 模拟五秒钟的网站搜索。
Thread.Sleep(2000);
r.links.Add( new link("网站 - 元素周期表:锡", "/") );
Thread.Sleep(1000);
r.links.Add( new link("网站 - 欢迎来到 Tin Lam 的主页", "/") );
Thread.Sleep(1000);
r.links.Add( new link("网站 - eBay 有大量锡玩具特价", "/") );
Thread.Sleep(1000);
r.links.Add( new link("网站 - 锡杯:电影原声带", "/") );
r.message = "找到: " + r.links.Count;
r.isCompleted = true;

} catch(Exception e) {

r.isCompleted = false;
r.message = "失败";

} finally {
// 释放连接资源
// 然后添加结果
addResult(r);
}
}
}

下图说明了 linearSearch 的工作原理

1

_results 是一个 ArrayList 的 result 对象,由搜索方法添加,分别在图中表示为 result1、result2 和 result3。search1、search2 和 search3 分别表示 searchWebSites()、searchDatabases() 和 searchDirectoryServers()。这三个方法做四件事:

  • 创建 result 对象,将其搜索名称传递给其构造函数。
  • 使用 Try,我们添加一些静态值作为链接对象到结果中,并使用 Thread.Sleep() 进行一些延迟。在真实世界中,您会希望初始化并打开与资源的适当连接,例如数据库连接等,然后处理并将服务器的真实结果添加到 result 对象的链接属性中。
  • 使用 Catch,我们处理从 Try 块抛出的任何异常。在这里,我们只是将 isCompleted 变量设置为 false,并显示一条消息,说明它失败了。在真实世界中,您可能想做更多的事情。也许将错误消息记录到某个地方,发送电子邮件将其报告给负责它的人,或者找到一些方法从错误中恢复并继续搜索,例如尝试在一些备份服务器上搜索等。您可能还希望扩展 catch 块以包含更多特定于异常的捕获——也许 searchDatabases() 中的一些与数据库相关的异常,以及 searchWebSites() 中的一些 WebRequest 和网络相关的异常。
  • 使用 Finally,我们释放并关闭所有与资源建立的连接。然后我们将结果添加到 _results 属性中。

由于我们也对部分搜索结果感兴趣,我们将结果对象 r 添加到 finally 块中的 _results 中。例如,如果我们要遍历 DataReader 并将每一行添加为链接到 links 集合,并且突然在第 11 行抛出了一个异常,那么我们仍然可以拥有前 10 个链接。但是,如果我只想要完整的搜索结果,那么我们就会在 try 块的末尾调用 addResult()。

search1.aspx


<%@ Page Language = "C#" %>

<script language="C#" runat="server">

linearSearch s;
ArrayList results;
result r;
link l = null;

protected void Page_Load(object sender, System.EventArgs e) {
s = new linearSearch();
results = s.search("tin");
}

</script>

<html>
<head><title>线性搜索</title></head>
<body>

<% for ( int i = 0; i < results.Count; i ++ ) {
r = (result)results[i]; %>
<b><%= r.label %></b> - <i><%= r.message %></i>
<% for ( int j = 0; j < r.links.Count; j ++ ) {
l = (link)r.links[j]; %>
<li><a href="<%= l.url %>"><%= l.name %></a>
<% } %>
<br>时间: <%= r.timeSpent.Seconds %> 秒。
<p>
<% } %>

<hr>
总时间: <%= s.totalTime.Seconds %> 秒。

</body>
</html>

ArrayList results = s.search("tin"); - 我们立即创建 linearSearch 对象并调用其 search() 方法,传入字符串 "tin"。在所有 .ASPX 文件中,我们都将静态字符串 "tin" 传递给搜索方法。在实际应用中,您可能希望在网页或 Windows Form 中设置一个表单,让用户输入要搜索的关键字。当搜索方法完成时,我们将得到一个 result 对象的 ArrayList。然后我们只需简单地遍历 ArrayList 并显示所有链接。当您运行此页面时,假设您已正确设置所有内容,您将看到以下内容:

2

正如您从图表和屏幕截图,或者仅仅从代码中看到的那样,此搜索所需的总时间是 9 秒。网站 5 秒,数据库 3 秒,目录 1 秒。但是请考虑一下:由于数据库可能在另一个服务器上,而目录在另一个服务器上,那么让目录搜索等待数据库搜索完成的意义何在?此外,目录服务器上的搜索只需要 1 秒,那么为什么它必须等待 8 秒,直到其他两个搜索都完成?即使在这漫长的 8 秒内它什么都没做?此外,如果其他两种方法有任何问题,比如当数据库搜索方法尝试连接到过载的数据库服务器时,那么目录搜索方法甚至必须等待数据库搜索方法等待数据库服务器!

并行搜索的搜索引擎

如果我们能同时启动所有搜索,然后按照它们返回的顺序,以先到先得的方式处理结果,那么搜索效率会高得多。线程可以实现这一点。在 Windows 中操作线程并不容易:VB6 及更早版本缺乏线程功能,需要第三方组件和一些不稳定的变通方法;C++ 通常不适合学习时间框架和开发计划;Java/VJ++ 也可能不是理想的选择。System.Threading 命名空间中提供的线程模型使线程操作变得容易得多,但它仍然非常强大。我们可以为每个搜索创建一个线程,并同时启动它们,而不是让它们都等待前面的搜索返回。我们还可以随着每个搜索的返回处理结果,因此无论哪个搜索首先完成,其结果都将首先得到处理,而无需关心其他搜索是否已完成(甚至成功)。

下图说明了 parallelSearch 的工作原理。

3

parallelSearch.cs


using System;
using System.Threading; // for Threading
using System.Collections; // for ArrayList

public class parallelSearch {

private ArrayList _results = null;
private string _keyword;
private DateTime _started;
private DateTime _end;

private void addResult(result thisResult) {
lock( _results ) {
_results.Add( thisResult );
}
}

public TimeSpan totalTime {
get { return _end.Subtract( _started ); }
}

public parallelSearch() {}

// 为了向后兼容
public ArrayList search(string keyword) {
return search(keyword, 0);
}

public ArrayList search(string keyword, int maxMilliSeconds) {

if ( keyword == null || keyword == "" )
return null;

_started = DateTime.Now;
_keyword = keyword;
_results = new ArrayList();

// 创建包装搜索方法的线程
Thread ps1 = new Thread( new ThreadStart(searchWebSites) );
Thread ps2 = new Thread( new ThreadStart(searchDatabases) );
Thread ps3 = new Thread( new ThreadStart(searchDirectoryServers) );

// 调用 Start() 方法开始执行方法
ps1.Start();
ps2.Start();
ps3.Start();

// 如果 maxMilliSeconds 大于 0,
// 那么我们想在线程上执行带超时的 Join()。
if ( maxMilliSeconds > 0 ) {

TimeSpan lDelta;
DateTime lStarted = DateTime.Now;

// 等待线程在超时内完成,否则中止它
if ( !ps3.Join(maxMilliSeconds) )
ps3.Abort();

// 计算第二个线程的剩余时间
lDelta = DateTime.Now.Subtract( lStarted );
maxMilliSeconds -= lDelta.Seconds * 1000 + lDelta.Milliseconds;
lStarted = DateTime.Now;
maxMilliSeconds = maxMilliSeconds > 0 ? maxMilliSeconds : 0;

// 等待第二个线程在剩余时间内完成,否则中止它
if ( !ps2.Join(maxMilliSeconds) )
ps2.Abort();

// 计算第三个线程的剩余时间
lDelta = DateTime.Now.Subtract( lStarted );
maxMilliSeconds -= lDelta.Seconds * 1000 + lDelta.Milliseconds;
maxMilliSeconds = maxMilliSeconds > 0 ? maxMilliSeconds : 0;

// 等待第三个线程在剩余时间内完成,否则中止它
if ( !ps1.Join(maxMilliSeconds) )
ps1.Abort();
}

// 如果我们中止了它们,等待它们自行清理,
// 或者如果 maxMilliSeconds <= 0,则无限期等待它们完成。
ps1.Join();
ps2.Join();
ps3.Join();

_end = DateTime.Now;
return _results;
}

private void searchDatabases() {

result r = new result("数据库服务器中“" + _keyword + "”的结果");
try {
// 在实际场景中,您会希望连接到一些数据库,
// 并执行一些真实的搜索。但在这里我们只使用一些静态值,
// 并使用 Thread.Sleep() 模拟一些延迟,使其看起来像是
// 实际上正在做一些耗时的工作。

// 从公司的图书库存数据库中找到 3 条记录
// 模拟 3 秒的数据库搜索
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《锡公主》", "/") );
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《奥兹国的锡木匠》", "/") );
Thread.Sleep(1000);
r.links.Add( new link("图书 - 《锡人》", "/") );
r.message = "找到: 共 " + r.links.Count + " 条";
r.isCompleted = true;

} catch(ThreadAbortException tae) {

r.isCompleted = false;
r.message = "找到: 在中止前共 " + r.links.Count + " 条";

} catch(Exception e) {

r.isCompleted = false;
r.message = "找到: 在失败前共 " + r.links.Count + " 条";

} finally {
// 释放数据库连接资源
// 然后添加结果
addResult(r);
}
}

private void searchDirectoryServers() {

result r = new result("目录服务器中“" + _keyword + "”的结果");
try {
// 从公司员工目录中找到 2 条记录
// 模拟一秒钟的搜索。
Thread.Sleep(1000);
r.links.Add( new link("员工 - Christine Yakukilu", "/") );
r.links.Add( new link("员工 - Tin Lam", "/") );
r.message = "找到: 共 " + r.links.Count + " 条";
r.isCompleted = true;

} catch(ThreadAbortException tae) {

r.isCompleted = false;
r.message = "找到: 在中止前共 " + r.links.Count + " 条";

} catch(Exception e) {

r.isCompleted = false;
r.message = "找到: 在失败前共 " + r.links.Count + " 条";

} finally {
// 释放目录服务器连接资源
// 然后添加结果
addResult(r);
}
}

private void searchWebSites() {

result r = new result("网站中“" + _keyword + "”的结果");
try {
// 从网络上找到 4 个网站
// 模拟五秒钟的网站搜索。
Thread.Sleep(1000);
r.links.Add( new link("网站 - 元素周期表:锡", "/") );
Thread.Sleep(1000);
r.links.Add( new link("网站 - 欢迎来到 Tin Lam 的主页", "/") );
Thread.Sleep(1000);
r.links.Add( new link("网站 - eBay 有大量锡玩具特价", "/") );
Thread.Sleep(2000);
r.links.Add( new link("网站 - 锡杯:电影原声带", "/") );
r.message = "找到: 共 " + r.links.Count + " 条";
r.isCompleted = true;

} catch(ThreadAbortException tae) {

r.isCompleted = false;
r.message = "找到: 在中止前共 " + r.links.Count + " 条";

} catch(Exception e) {

r.isCompleted = false;
r.message = "找到: 在失败前共 " + r.links.Count + " 条";

} finally {
// 释放连接资源
// 然后添加结果
addResult(r);
}
}
}

这里的一切都与 linearSearch.cs 中相同,除了以下几点:

1. 对 _result 的同步访问


lock( _results ) {
_results.Add( thisResult );
}

由于所有搜索方法都是并发运行的,因此很有可能不止一个方法会完成其搜索并尝试将其结果对象添加到 _results 中。就像 System.Collections 命名空间中的所有 Collection 类一样,ArrayList 默认不是线程安全的。如果我们像 linearSearch 中那样保留它,那么当两个或更多线程同时调用它们的 Add() 时,我们可能会遇到一些问题。例如,_results 中的值可能会损坏。然而,通过对 _results 添加锁,.NET 运行时将为我们强制执行对其的同步访问。也就是说,第一个线程进入方法,并获取对 _results 的锁。当第二个线程进入方法并尝试获取对 _results 的锁时,运行时将阻止它这样做,并强制它等待直到第一个线程释放锁。或者,您可以使用 ArrayList 类中的此方法使 ArrayList 线程安全:public static Arraylist Synchronized(ArrayList list);将其 _results 作为参数传入,并使其返回一个线程安全的 ArrayList。此方法基本上将一个 ArrayList 对象包装起来,并确保其所有方法和属性在任何给定时间都不会被超过一个线程访问。

2. 为每个搜索创建和启动一个线程

Thread ps1 = new Thread( new ThreadStart(searchWebSites) );
Thread ps2 = new Thread( new ThreadStart(searchDatabases) );
Thread ps3 = new Thread( new ThreadStart(searchDirectoryServers) );

ps1.Start();
ps2.Start();
ps3.Start();

就像可执行文件有 Main() 作为其入口点一样,线程也有一个入口点。我们通过将它们的“入口点”传递给它们的构造函数来创建线程。这些“入口点”实际上是一些 ThreadStart 对象。ThreadStart 对象实际上只是一些委托对象,我们可以通过将我们希望调用的方法传递给它们的构造函数来创建它们。传递给 ThreadStart 的方法不能带有任何参数,也不能返回任何内容。这就是我们声明所有三个搜索方法为 void 且不带有任何参数的原因。然后我们调用它们的 Start() 方法来开始执行这些方法。当我们调用 Start() 方法时,运行时会将线程切换到“运行”状态,以便操作系统可以调度它们执行。然后,当线程开始执行并调用给定的方法时,这些方法将返回。然后运行时将继续执行下一行代码。

3. 中止线程

if ( !ps3.Join(maxMilliSeconds) )
ps3.Abort();

如果 maxMilliSeconds 值大于 0,则表示指定了超时,如果线程超过超时,我们希望中止它们。我们通过首先在一个线程上调用 Join() 方法来计时超时。Join() 有三个重载签名,在这种情况下我们使用的是这个:public bool Join(int millisecondsTimeout)。如果线程在 millisecondsTimeout 期限内完成执行,则返回 true;如果线程在此期限后仍未完成,则返回 false。在这里,如果线程在 maxMilliSeconds 之前完成,则我们不理会它,否则,我们调用其 Abort() 方法来中止它。

lDelta = DateTime.Now.Subtract( lStarted );
maxMilliSeconds -= lDelta.Seconds * 1000 + lDelta.Milliseconds;
lStarted = DateTime.Now;
maxMilliSeconds = maxMilliSeconds > 0 ? maxMilliSeconds : 0;

我们使用两个局部变量 lDelta 和 lStarted 来跟踪时间,以便计算我们等待第一个线程完成所花费的时间。我们可以等待第二个线程的时间是原始 maxMilliSeconds 减去我们等待第一个线程的时间。请注意,maxMilliSeconds 可能会变成负值。例如,如果我们将所有 3500 毫秒都花在等待第一个线程上,则 lDelta 的时间跨度将是调用 Join() 的值,再加上 CPU 花在其他语句上的时间。因此,如果我们只取 maxMilliSeconds 减去从 lDelta 中获得的毫秒数,我们将得到一个负值。因此,如果出现这种情况,我们将其设置为 0。由于所有线程都在同时运行,如果其中一个线程超过了 maxMilliSeconds 期限,则意味着所有其他线程要么已经返回,要么它们也已经运行了 maxMilliSeconds。因此,当我们对其他线程调用 Join(0) 时,我们希望它们立即返回 true,否则我们也会中止它们。我们首先对第三个线程调用 Join(),然后是第二个,然后是第一个,以证明这些计算确实有效。

4. 捕获 ThreadAbortException

} catch(ThreadAbortException tae) {

r.isCompleted = false;
r.message = "找到: 在中止前共 " + r.links.Count + " 条";

现在,关于 ThreadAbortException 的文档存在一些不一致之处。在 .NET beta 2 文档中,它指出:“ ThreadAbortException 是一种特殊的异常,无法捕获。”然而,在 .NET RC0 文档中,它指出:“ 当在线程上调用此方法时,系统会在线程中抛出 ThreadAbortException 以中止它。ThreadAbortException 是一种特殊异常,可以由应用程序代码捕获,但在 catch 块结束时会重新抛出,除非调用 ResetAbort。” “ 当此方法 .”指的是 Abort(),所以似乎 RC0 文档在这里更准确、更精确。本文中的示例清楚地展示了我们如何捕获此异常。因此,当抛出此异常时,程序会离开 try 块并进入 catch 块,以设置一个属性来指示搜索未完成,并且消息指示它已中止。最后,执行 finally 块,并调用 addResult()。在 linearSearch 类中,我们可以继续向 links 集合添加链接,直到全部添加完毕,或者抛出异常。在 parallelSearch 类中,即使代码本身没有错误,但如果超时,也会抛出 ThreadAbortException。但是,我们仍然会拥有在超时之前已添加到 links 集合中的链接。

5. 等待线程返回

ps1.Join();
ps2.Join();
ps3.Join();

这里我们使用 Thread.Join() 的另一个签名:public void Join()。当在线程上调用此方法时,调用方法(在本例中是我们的 search())将无限期等待线程完成其执行,因为我们希望确保所有三个线程在返回结果之前完全停止(无论是完成还是中止)。因此我们再次调用它们的 Join()。即使我们中止线程,我们仍然希望它们在 catch 和 finally 块中完成清理工作。这样,我们可以确保在将方法返回给调用者之前,不仅清理工作已完成,而且结果对象也都已添加到 _results 中。

search2.aspx

<%@ Page Language = "C#" %>

<script language="C#" runat="server">

parallelSearch s;
ArrayList results;
result r;
link l = null;

protected void Page_Load(object sender, System.EventArgs e) {
s = new parallelSearch();
results = s.search("tin");
}

</script>

<html>
<head><title>并行搜索</title></head>
<body>

<% for ( int i = 0; i < results.Count; i ++ ) {
r = (result)results[i]; %>
<b><%= r.label %></b> - <i><%= r.message %></i>
<% for ( int j = 0; j < r.links.Count; j ++ ) {
l = (link)r.links[j]; %>
<li><a href="<%= l.url %>"><%= l.name %></a>
<% } %>
<br>时间: <%= r.timeSpent.Seconds %> 秒。
<p>
<% } %>

<hr>
总时间: <%= s.totalTime.Seconds %> 秒。

</body>
</html>

一切都与 search1.aspx 相同,除了对象 s 是用不同的类创建的,s = new parallelSearch()。当您运行此页面时,假设您已正确设置所有内容,您将看到以下内容:

4

现在更有意义了,搜索的总时间应该与最长的搜索时间大致相同,即 searchWebSites() 的 5 秒。请注意,我们仍然拥有完全相同的结果!

带超时的并行搜索搜索引擎

我们能够中止线程,这意味着如果搜索超出超时,我们可以中止它们。请注意,我们也可以在 linearSearch 中通过一些额外的计时计算和条件检查来中止搜索。但是在这两种情况下,搜索都不会在超时时完全停止。这也取决于 catch 和 finally 块中的处理。但在大多数情况下,线程会几乎立即停止,并且延迟不会很明显。

下图说明了带超时的并行搜索的工作原理

5

search3.aspx

<%@ Page Language = "C#" %>

<script language="C#" runat="server">

parallelSearch s;
ArrayList results;
result r;
link l = null;

protected void Page_Load(object sender, System.EventArgs e) {
s = new parallelSearch();
results = s.search("tin", 3500);
}

</script>

<html>
<head><title>带超时的并行搜索</title></head>
<body>

<% for ( int i = 0; i < results.Count; i ++ ) {
r = (result)results[i]; %>
<b><%= r.label %></b> - <i><%= r.message %></i>
<% for ( int j = 0; j < r.links.Count; j ++ ) {
l = (link)r.links[j]; %>
<li><a href="<%= l.url %>"><%= l.name %></a>
<% } %>
<br>时间: <%= r.timeSpent.Seconds %> 秒 <%= r.timeSpent.Milliseconds %> 毫秒。
<p>
<% } %>

<hr>
总时间: <%= s.totalTime.Seconds %> 秒 <%= s.totalTime.Milliseconds %> 毫秒。

</body>
</html>

唯一的区别是 results = s.search("tin", 3500) ; 中的超时值额外参数 3500;这里我们将超时设置为 3.5 秒进行搜索。我们在这里使用相同的 parallelSearch 类。当您运行它时,假设您已正确设置所有内容,您将看到以下内容:

6

 

 

摘要

 

如您所见,线程模型使我们能够构建更高效、响应更迅速的软件。它还使我们能够构建更快、更智能的搜索引擎。然而,在我们的示例中,使用线程的优势来自于所有资源都位于不同机器上的事实。如果资源位于同一机器上,则此方法的优势可能很小。另一方面,如果要搜索的位置太多,那么为每个位置创建一个线程也可能会降低性能。例如,searchWebSites() 可能会搜索数千个网站。如果您只为每个搜索创建一个线程,那么 CPU 可能会在几分钟内一直进行线程切换。在这种情况下,您可能希望利用线程池。

© . All rights reserved.