最近在尝试把leveldb的移植到java上。对移植的leveldb测试写速度,测试场景分别用1,2,3,4同时并发压,每个线程写100w条数据。测试完成时间,结果是:1线程23s,2线程21s,3线程27s,4线程35s。1线程使用的时间出乎我的意料。一开始是怀疑在移植时,io的实现出问题,把写文件的操作屏蔽了,每个write操作到io那层都是马上返回。4个场景的完成时间都有所减少,但是1线程仍然和2线程耗时差不多。
先来看下leveldb的原生实现。leveldb是支持并发访问write的,在write的某些步骤(例如写log)用互斥锁来实现。每个write操作都会导致一次io调用。如果在数据体较少,但数据量非常大的情况下,频繁的io调用占的开销就很可观了。在移植时考虑到这个问题,实现了一个合并写的策略。描述如下:
在并发的write操作时,和leveldb的处理方法不同,每个write线程做完预处理后,提交一个write log请求到一个阻塞队里,然后阻塞等待,由一个专有线程不断从队里取出请求,在某些策略下(例如如果一个请求的数据非常小,并且队列不为空),合并请求的数据,一并写到log里。请求被处理后,唤醒write线程,write线程返回,至此一个write操作完成。
这种策略在高并发情况下,可以有效减少io次数。并且这个方案代码非常简单,几乎没有锁,因为在可能冲突的情况都是专有线程单线程处理。
问题应该是出在这个方案里,简单的用leveldb的原生思路替代这个方案后,1线程只需用8s。这个简单方案没考虑并发,多线程的场景就没有测。
继续分析合并写的方案。把write线程阻塞等待改为yield后,再对4个场景测试,耗时进一步减少,但1线程和2线程仍然没区别。更进一步修改,专有线程在阻塞队列为空时,不进入阻塞,采用yield。再测试,1线程耗时8秒。终于得到想要的结果。
在1个线程的场景下,专有线程在唤醒write线程后再取请求时都会进入阻塞状态。这是导致1个线程场景如此慢的原因。并发上去了,进入阻塞的次数就少了,所以出现1,2,3线程没有太大的差距。这里线程阻塞唤醒的开销之大出乎我的意料。
通过测试证明这种方案是不可取的,当write的操作不频繁时,会导致每个write时延非常高。
替代方案:
每个write线程做完预处理后,仍然是提交write请求到一个队列,区别是这里没有一个专有线程负责写log,write线程继续尝试竞争一个锁,成功的进入写log操作,从队里取出请求,适当合并数据,写log。竞争失败的线程检查自己的请求是否已被处理,是则返回,否则重复一定次数的yield。这种方案的成绩比较理想,4个场景分别为8s,15s,23s,28s。
附:后来看一篇 Martin Fowler介绍的一个lmax的玩意(http://martinfowler.com/articles/lmax.html)号称每秒能处理600w笔业务。好奇下,把里面的并发框架源码简单抠了下。简单来说,它的流程也是异步的,提交,等待。它里面的很多等待都有yield策略。如果针对cpu核数,适当的分配处理线程,使用yiled来避免阻塞,应该是一个应付高负载的较好的方案。当然,文章里还提到如此高的处理效率还得益于尽量避免cacheline失效。
分享到:
相关推荐
在Java中,可以使用`Thread.suspend()`方法来挂起线程,但需要注意,这个方法会使得线程陷入阻塞状态,直到被其他线程唤醒。然而,`Thread.suspend()`和相应的`Thread.resume()`方法已不推荐使用,因为它们可能导致...
通常,线程休眠不会导致CPU忙等待,而是将线程的状态改为不可运行,释放CPU资源。当指定的事件发生或时间到达时,操作系统会唤醒该线程,将其重新放入就绪队列,等待调度器分配执行时间。 实验中涉及的几个测试用例...
`V`操作会将信号量的值加一,如果信号量的值变为非负数,那么就会唤醒一个被阻塞的进程或线程。 ### 实际应用示例 1. **独木桥问题** 在独木桥问题中,我们利用信号量来确保独木桥上的通行规则。对于每次只允许...
在实际开发中,正确使用这些线程通信机制可以避免数据不一致、死锁等问题,确保多线程程序的正确性和效率。例如,在生产者-消费者模型或读者-写者问题中,线程间的通信是解决并发问题的关键。因此,理解并熟练掌握...
2. 条件变量(Condition Variable):用于线程间通信,当满足特定条件时,一个线程可以唤醒另一个线程。 3. 信号量(Semaphore):控制对共享资源的访问数量,超过设定值时,其他线程将被阻塞。 四、线程优缺点 ...
在编程领域,多线程是提高程序执行效率的重要手段,特别是在现代处理器的多核环境下,多线程可以充分利用硬件资源,实现并发执行任务。然而,多线程也带来了一些问题,比如数据竞争、死锁等,这就需要我们采取措施...
由于多个线程可以共享进程的内存和资源,因此在多线程环境下需要考虑同步问题,以防止多个线程同时访问同一资源导致数据不一致。Java提供了synchronized关键字来控制方法或者代码块的并发访问,确保同一时刻只有一个...
总结来说,"使用阻塞模式、完成端口的多线程socket iocp"是一种通过IOCP实现的高效Socket服务器模型,它结合了阻塞模式的简单性和非阻塞模式的效率,利用多线程并发处理多个连接,适用于大规模、高并发的网络服务。...
线程等待就是一种解决这种问题的方法,确保一个线程完成其任务后再执行其他线程。 在描述的示例中,我们首先导入了`threading`和`time`模块。定义了一个名为`thread_job`的函数,它打印"T1 start",然后暂停0.1秒,...
线程死锁是多线程编程中一个严重的问题,它发生在两个或多个线程相互等待对方释放资源,导致它们都无法继续执行。这个概念在计算机科学中尤为重要,因为并发执行是提高系统性能的关键手段,但如果不妥善处理,死锁...
在C#编程中,多线程技术是一种关键的并发处理机制,它允许程序同时执行多个独立的任务,从而提高系统的效率和响应性。本资源“C#多线程编程实战Code源代码”来源于华章出版社,提供了丰富的实例来帮助开发者深入理解...
Java多线程是Java编程语言中的一个重要特性,它允许在单个程序中同时执行多个线程,从而提高程序的效率和响应性。本文将深入探讨Java多线程的基础概念、线程的生命周期以及一些关键的线程控制方法,如`suspend()`和`...
- I/O操作,如读写文件,也可能导致线程阻塞。 5. **守护线程(Daemon Thread)** - 守护线程是为其他线程服务的线程,如垃圾收集器。 - 当所有非守护线程结束时,Java虚拟机会停止执行守护线程并退出。 - 使用...
线程优先级包括`Lowest`、`BelowNormal`、`Normal`、`AboveNormal`、`Highest`,但过度依赖优先级可能会导致线程饥饿问题,应谨慎使用。 7. **线程池**: - .NET框架提供了一个线程池,它管理一组线程以供重复使用...
Java多线程是Java编程中的重要概念,它允许程序同时执行多个任务,提高了程序的运行效率和资源利用率。本笔记全面涵盖了多线程的学习,包括基础理论和实践代码,旨在帮助开发者深入理解并掌握Java多线程技术。 一、...
3. **条件变量**:条件变量允许线程在满足特定条件时阻塞自己,条件一旦满足,线程会被唤醒。在Java中,`ReentrantLock`的`Condition`接口可以实现这个功能。在这个问题中,我们可能不需要条件变量,因为票的数量是...
然而,这可能会引入线程阻塞,降低系统并行度。 2. **读写锁(Read-Write Lock)**:对于读操作多而写操作少的情况,读写锁可以提高效率。多个读线程可以同时进行,但写线程会独占锁,其他线程必须等待。 3. **...
2. 如果任务执行时间过长,可能导致其他任务长时间等待,这时应考虑使用单独的线程来避免阻塞线程池资源。 3. 当需要将线程绑定到特定的单线程单元(如UI线程)时,线程池中的线程是多线程单元,无法满足此类需求。 ...
这样可以保证程序的运行效率,避免线程长时间阻塞导致的资源浪费。 易语言线程挂起与恢复的源码示例提供了具体的实现方法,通过分析这些代码,我们可以了解到如何在实际编程中有效地控制线程状态。例如,一个简单的...