【问题标题】:Why multithreading doesn't make the execution faster为什么多线程不会使执行更快
【发布时间】:2022-09-24 17:32:12
【问题描述】:

所以我有这个实验代码:

    class WorkLoader : Runnable {
    private val id : Int
    private val listener : Listener?
    private val lock : ReentrantLock
    private val condition : Condition
    private val counter : Counter?

    private var isFinished : Boolean

    constructor(counter: Counter? = null, listener: Listener? = null) {
        id = IdGenerator.getId()
        isFinished = false
        lock = ReentrantLock()
        condition = lock.newCondition()
        this.counter = counter
        this.listener = listener
    }

    interface Listener {
        fun onWorkStarted(id : Int)
        fun onWorkFinished(id : Int, s : String, elapsed : Long)
    }

    override fun run() {
        listener?.onWorkStarted(id)
        val startTime = System.currentTimeMillis()

        //The loop below just simply loads the CPU with useless stuff, it does nothing important
        var s = \"\"
        for (i in 1 .. 10_000_000) {
            counter?.add()
            val c : Char = (i % 95 + 32).toChar()
            s += c
            if (s.length > 200) {
                s = s.substring(1)
            }
        }

        val elapsedTime = System.currentTimeMillis() - startTime
        listener?.onWorkFinished(id, s, elapsedTime)

        lock.lock()
        isFinished = true
        condition.signal()
        lock.unlock()
    }

    fun waitTillFinished() {
        lock.lock()
        while (!isFinished) {
            condition.await()
        }
        lock.unlock()
    }
}

以及在 6 个独立线程中同时运行 6 个 WorkLoader 实例的主要功能:

    fun main(arguments: Array<String>) {
    println(\"Hello World!\")

    val workListener = WorkLoaderListener()
    
    val workers = ArrayList<WorkLoader>()
    for (i in 1..6) {
        val workLoader = WorkLoader(counter = null, workListener)
        workers.add(workLoader)

        val thread = Thread(workLoader)
        thread.start()
    }

    for (worker in workers) {
        worker.waitTillFinished()
    }
    
    println(\"End of main thread\")
}

class WorkLoaderListener : WorkLoader.Listener {

    override fun onWorkStarted(id: Int) {
        println(\"Work started, id:$id ${getFormattedTime()}\")
    }

    override fun onWorkFinished(id: Int, s: String, elapsed : Long) {
        println(\"Work ENDED,   id:$id ${getFormattedTime()}, in ${elapsed/1000} s\")
    }
}

让所有 6 个线程完成执行需要 8 秒。这是输出:

Hello World!
Work started, id:1 21:12:26.577
Work started, id:0 21:12:26.577
Work started, id:2 21:12:26.577
Work started, id:4 21:12:26.577
Work started, id:5 21:12:26.577
Work started, id:3 21:12:26.577
Work ENDED,   id:2 21:12:35.137, in 8 s
Work ENDED,   id:1 21:12:35.137, in 8 s
Work ENDED,   id:3 21:12:35.215, in 8 s
Work ENDED,   id:0 21:12:35.215, in 8 s
Work ENDED,   id:5 21:12:35.215, in 8 s
Work ENDED,   id:4 21:12:35.231, in 8 s
End of main thread

然而!!!单独线程中只有 1 个 WorkLoader 实例在 1 秒内执行。这使得一个一个地运行这些线程而不是同时午餐它们更有效。 像这样:

for (i in 1..6) {
    val workLoader = WorkLoader(counter = null, workListener)
    workers.add(workLoader)

    val thread = Thread(workLoader)
    thread.start()
    //just one extra line to wait for the termination before starting another workLoader
    workLoader.waitTillFinished() //I understand that the workLoader thread might still be running when this method returns, 
// but it doesn\'t matter, the thread is about to die anyway
}

输出:

Hello World!
Work started, id:0 21:23:33.622
Work ENDED,   id:0 21:23:35.411, in 1 s
Work started, id:1 21:23:35.411
Work ENDED,   id:1 21:23:36.545, in 1 s
Work started, id:2 21:23:36.545
Work ENDED,   id:2 21:23:37.576, in 1 s
Work started, id:3 21:23:37.576
Work ENDED,   id:3 21:23:38.647, in 1 s
Work started, id:4 21:23:38.647
Work ENDED,   id:4 21:23:39.687, in 1 s
Work started, id:5 21:23:39.687
Work ENDED,   id:5 21:23:40.726, in 1 s
End of main thread

所以在这种情况下,整个程序的执行在 6 或 7 秒内结束。我有一个带有 12 个逻辑线程的 6 核英特尔 CPU。所以我希望最多在 2 秒内执行所有 6 个线程(当一次全部启动时)。在第一种情况下(一次所有线程),CPU 的利用率飙升至 100%,并且在整个执行期间都保持在那里。在第二种情况下(一次一个线程),CPU 在短时间内飙升至 47%,整个执行速度稍快。

那么多线程有什么意义呢?为什么会这样?感觉拥有超过 1 个工作线程是没有意义的,因为任何额外的线程都会使所有其他线程变慢,无论您​​有多少 CPU 内核可供使用。如果一个线程能够使用 CPU 的所有内核,那么为什么我的 CPU 在第二种情况下没有达到 100% 负载呢?

  • 删除了 [java] 标签,因为代码是 Kotlin。
  • 你是如何进行测试的?您是否使用了基准测试框架,或者至少进行了任何手动预热? (如果不是,您看到的时间可能根本无法反映您的代码的性能。)
  • 我很困惑,但我怀疑您对字符串所做的事情的性质由于某种原因对并行性不太友好。它正在创建可能的新字符串,所以可能会有一些内存和 GC 恶作剧影响它?

标签: multithreading kotlin


【解决方案1】:

@Tenfour04,谢谢!您的评论将我引向了正确的答案。多线程的目的是保存! 所以,显然我的字符串操作确实不是多线程友好的,不知道为什么。 所以我将我的 CPU 加载代码更改为:

val cArr = arrayOfNulls<Char>(200)
for (i in 1..20_000_000) {
    val cValue: Char = (i % 95 + 32).toChar()
    if (cArr[0] == null) {
        cArr[0] = cValue
    } else {
        var tempC = cValue
        for (ci in cArr.indices) {
            val temp = cArr[ci]
            cArr[ci] = tempC
            if (temp == null) {
                break
            }
            tempC = temp
        }
    }
}

现在这段代码在 3 秒内在 1 个线程上执行。正如您在下面的输出中看到的:

Hello World!
All threads initiated
Work started, id:0 02:16:58.000
Work ENDED,   id:0 02:17:01.189, in 3 s
End of main thread

现在有6个线程:

Hello World!
All threads initiated
Work started, id:0 02:18:48.830
Work started, id:2 02:18:48.830
Work started, id:1 02:18:48.830
Work started, id:5 02:18:48.830
Work started, id:4 02:18:48.830
Work started, id:3 02:18:48.830
Work ENDED,   id:1 02:18:53.090, in 4 s
Work ENDED,   id:0 02:18:53.168, in 4 s
Work ENDED,   id:3 02:18:53.230, in 4 s
Work ENDED,   id:4 02:18:53.246, in 4 s
Work ENDED,   id:2 02:18:53.340, in 4 s
Work ENDED,   id:5 02:18:53.340, in 4 s
End of main thread

工作量的 6 倍,但总执行和等待时间只增加了 1 秒,我的 CPU 有 12 个逻辑线程,所以......

Hello World!
All threads initiated
Work started, id:1 02:22:43.299
Work started, id:8 02:22:43.299
Work started, id:3 02:22:43.299
Work started, id:5 02:22:43.299
Work started, id:7 02:22:43.299
Work started, id:9 02:22:43.299
Work started, id:11 02:22:43.299
Work started, id:10 02:22:43.299
Work started, id:0 02:22:43.299
Work started, id:2 02:22:43.299
Work started, id:4 02:22:43.299
Work started, id:6 02:22:43.299
Work ENDED,   id:4 02:22:50.115, in 6 s
Work ENDED,   id:11 02:22:50.132, in 6 s
Work ENDED,   id:7 02:22:50.148, in 6 s
Work ENDED,   id:1 02:22:50.148, in 6 s
Work ENDED,   id:6 02:22:50.148, in 6 s
Work ENDED,   id:9 02:22:50.148, in 6 s
Work ENDED,   id:10 02:22:50.163, in 6 s
Work ENDED,   id:5 02:22:50.163, in 6 s
Work ENDED,   id:0 02:22:50.179, in 6 s
Work ENDED,   id:8 02:22:50.195, in 6 s
Work ENDED,   id:2 02:22:50.195, in 6 s
Work ENDED,   id:3 02:22:50.210, in 6 s
End of main thread

现在,如果我超过 12 个线程,这比我的 CPU 的逻辑线程数量多,时间会大大增加并且效率会降低。我不会在这个上发布输出,太多行,所以相信我。

【讨论】:

    猜你喜欢
    • 2017-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    • 2021-12-14
    相关资源
    最近更新 更多