[Lucene.Net] 多线程操作建议和[Lucene.Net] 分页显示
对于并发,Lucene.Net 遵循以下规则:
1. 允许任意多的读操作并发,即任意数量用户可同时对同一索引做检索操作。
2. 即便正在进行索引修改操作(索引优化、添加文档、删除文档),依然允许任意多的检索操作并发执行。
3. 不允许并发修改操作,也就是说同一时间只允许一个索引修改操作。
Lucene.Net 内部已经对多线程安全进行了处理,打开 IndexWrite.cs / IndexReade.csr 文件,会发现很多操作都使用了 lock 进行多线程同步锁定。只要遵循一定的规则,就可以在多线程环境下安全运行 Lucene.Net。
建议:
1. Directotry、Analyzer 都是多线程安全类型,只需建立一个 Singleton 对象即可。
2. 所有线程使用同一个 IndexModifier 对象进行索引修改操作。
3. IndexWriter/IndexReader/IndexModifier/IndexSearcher 最好使用同一个 Directory 对象,否则多线程并发读写时可能引发 FileNotFoundException。
IndexModifier 对象封装了 IndexWriter 和 IndexReader 的常用操作,其内部实现了多线程同步锁定。使用 IndexModifier 可避免同时使用 IndexWriter 和 IndexReader 时需要在多个对象之间进行同步的麻烦。等所有修改操作完成后,记住调用 Close() 方法关闭相关资源。并不是每次操作都需要调用 Optimize(),可以依据特定情况,定期执行优化操作。
--------
以下演示代码简单封装了一个 IndexModifier Signleton 类型,确保多线程使用同一个对象,且只能由最后一个多线程调用 Close 方法关闭。
代码不完善,仅供参考!需要做些修改才能应用于实际项目。
多线程测试代码
1. 允许任意多的读操作并发,即任意数量用户可同时对同一索引做检索操作。
2. 即便正在进行索引修改操作(索引优化、添加文档、删除文档),依然允许任意多的检索操作并发执行。
3. 不允许并发修改操作,也就是说同一时间只允许一个索引修改操作。
Lucene.Net 内部已经对多线程安全进行了处理,打开 IndexWrite.cs / IndexReade.csr 文件,会发现很多操作都使用了 lock 进行多线程同步锁定。只要遵循一定的规则,就可以在多线程环境下安全运行 Lucene.Net。
建议:
1. Directotry、Analyzer 都是多线程安全类型,只需建立一个 Singleton 对象即可。
2. 所有线程使用同一个 IndexModifier 对象进行索引修改操作。
3. IndexWriter/IndexReader/IndexModifier/IndexSearcher 最好使用同一个 Directory 对象,否则多线程并发读写时可能引发 FileNotFoundException。
IndexModifier 对象封装了 IndexWriter 和 IndexReader 的常用操作,其内部实现了多线程同步锁定。使用 IndexModifier 可避免同时使用 IndexWriter 和 IndexReader 时需要在多个对象之间进行同步的麻烦。等所有修改操作完成后,记住调用 Close() 方法关闭相关资源。并不是每次操作都需要调用 Optimize(),可以依据特定情况,定期执行优化操作。
--------
以下演示代码简单封装了一个 IndexModifier Signleton 类型,确保多线程使用同一个对象,且只能由最后一个多线程调用 Close 方法关闭。
代码不完善,仅供参考!需要做些修改才能应用于实际项目。
public class MyIndexModifier
{
private static Directory directory = new RAMDirectory();
private static Analyzer analyzer = new StandardAnalyzer();
private static IndexModifier modifier;
private static List<Thread> threadList = new List<Thread>();
private MyIndexModifier() { }
public static IndexModifier GetInstance()
{
lock (threadList)
{
if (modifier == null)
{
modifier = new IndexModifier(directory, analyzer, false);
modifier.SetMaxFieldLength(1000);
}
if (!threadList.Contains(Thread.CurrentThread))
threadList.Add(Thread.CurrentThread);
return modifier;
}
}
public static void Close()
{
lock (threadList)
{
if (threadList.Contains(Thread.CurrentThread))
threadList.Remove(Thread.CurrentThread);
if (threadList.Count == 0)
{
if (modifier != null)
{
modifier.Close();
modifier = null;
}
}
}
}
}
{
private static Directory directory = new RAMDirectory();
private static Analyzer analyzer = new StandardAnalyzer();
private static IndexModifier modifier;
private static List<Thread> threadList = new List<Thread>();
private MyIndexModifier() { }
public static IndexModifier GetInstance()
{
lock (threadList)
{
if (modifier == null)
{
modifier = new IndexModifier(directory, analyzer, false);
modifier.SetMaxFieldLength(1000);
}
if (!threadList.Contains(Thread.CurrentThread))
threadList.Add(Thread.CurrentThread);
return modifier;
}
}
public static void Close()
{
lock (threadList)
{
if (threadList.Contains(Thread.CurrentThread))
threadList.Remove(Thread.CurrentThread);
if (threadList.Count == 0)
{
if (modifier != null)
{
modifier.Close();
modifier = null;
}
}
}
}
}
多线程测试代码
for (int i = 0; i < 100; i++)
{
new Thread(delegate()
{
IndexModifier writer = MyIndexModifier.GetInstance();
for (int x = 0; x < 10; x++)
{
Document doc = new Document();
doc.Add(Field.Text("a", "Hello, World!"));
writer.AddDocument(doc);
}
Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, writer.DocCount());
MyIndexModifier.Close(); // 注意不是调用 IndexModifier.Close() !
}).Start();
}
{
new Thread(delegate()
{
IndexModifier writer = MyIndexModifier.GetInstance();
for (int x = 0; x < 10; x++)
{
Document doc = new Document();
doc.Add(Field.Text("a", "Hello, World!"));
writer.AddDocument(doc);
}
Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, writer.DocCount());
MyIndexModifier.Close(); // 注意不是调用 IndexModifier.Close() !
}).Start();
}
很简单,依照以下公式计算几个数据即可。
索引库文档总数
DocumentCount = searcher.Reader.NumDocs();
搜索结果总数
Count = Hits.Length();
每页记录数
PageSize;
结果页总数 (注意处理余数)
PageCount = (Count / PageSize) + (Count % PageSize > 0 ? 1 : 0);
要显示的页序号 (当页序号大于页总数时,返回最后一页。)
PageIndex = Math.Min(PageIndex, PageCount);
起始记录序号 (起始序号必须大于等于零)
StartPos = Math.Max((PageIndex - 1) * PageSize, 0);
结尾记录序号 (不能大于记录总数)
EndPos = Math.Min(PageIndex * PageSize - 1, Count - 1);
DocumentCount = searcher.Reader.NumDocs();
搜索结果总数
Count = Hits.Length();
每页记录数
PageSize;
结果页总数 (注意处理余数)
PageCount = (Count / PageSize) + (Count % PageSize > 0 ? 1 : 0);
要显示的页序号 (当页序号大于页总数时,返回最后一页。)
PageIndex = Math.Min(PageIndex, PageCount);
起始记录序号 (起始序号必须大于等于零)
StartPos = Math.Max((PageIndex - 1) * PageSize, 0);
结尾记录序号 (不能大于记录总数)
EndPos = Math.Min(PageIndex * PageSize - 1, Count - 1);
演示:返回第10页,每页20条记录。
Hits hits = searcher.Search(query);
int pageIndex = 10;
int pageSize = 20;
int count = hits.Length();
if (count > 0)
{
int pageCount = count / pageSize + (count % pageSize > 0 ? 1 : 0);
pageIndex = Math.Min(pageIndex, pageCount);
int startPos = Math.Max((pageIndex - 1) * pageSize, 0);
int endPos = Math.Min(pageIndex * pageSize - 1, count - 1);
for (int i = startPos; i <= endPos; i++)
{
int docId = hits.Id(i);
string name = hits.Doc(i).Get(FieldName);
float score = hits.Score(i);
Console.WriteLine(" DocId:{0}; Name:{1}; Score:{2}", docId, name, score);
}
}
int pageIndex = 10;
int pageSize = 20;
int count = hits.Length();
if (count > 0)
{
int pageCount = count / pageSize + (count % pageSize > 0 ? 1 : 0);
pageIndex = Math.Min(pageIndex, pageCount);
int startPos = Math.Max((pageIndex - 1) * pageSize, 0);
int endPos = Math.Min(pageIndex * pageSize - 1, count - 1);
for (int i = startPos; i <= endPos; i++)
{
int docId = hits.Id(i);
string name = hits.Doc(i).Get(FieldName);
float score = hits.Score(i);
Console.WriteLine(" DocId:{0}; Name:{1}; Score:{2}", docId, name, score);
}
}
作者: yuhen
关注 熵减黑客 ,一起学习成长

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端