协程执行时间与回调执行时间

时间:2019-09-07 08:59:16

标签: kotlin kotlin-coroutines

我正在阅读“ Functional Kotlin”这本书,并且刚刚从该书中尝试了一些有关Kotlin中并发编程的示例。我有以下并发代码的实现:

  1. 带有回调:
package com.freesoft.functional.coroutines

import kotlin.concurrent.thread

class CallbacksMain

class CallbackUserClient(private val client: UserClient) {
    fun getUser(id: Int, callback: (User) -> Unit) {
        thread {
            callback(client.getUser(id))
        }
    }
}

class CallbackFactClient(private val client: FactClient) {
    fun get(user: User, callback: (Fact) -> Unit) {
        thread {
            callback(client.getFact(user))
        }
    }
}

class CallBackUserRepository(private val userRepository: UserRepository) {
    fun getUserById(id: UserID, callback: (User?) -> Unit) {
        thread {
            callback(userRepository.getUserById(id))
        }
    }

    fun insertUser(user: User, callback: () -> Unit) {
        thread {
            userRepository.insertUser(user)
            callback()
        }
    }
}

class CallBackFactRepository(private val factRepository: FactRepository) {
    fun getFactByUserId(id: Int, callback: (Fact?) -> Unit) {
        thread {
            callback(factRepository.getFactByUserID(id))
        }
    }

    fun insertFact(fact: Fact, callback: () -> Unit) {
        thread {
            factRepository.insertFact(fact)
            callback()
        }
    }
}

class CallBackUserService(
    private val userClient: CallbackUserClient,
    private val factClient: CallbackFactClient,
    private val userRepository: CallBackUserRepository,
    private val factRepository: CallBackFactRepository
) : UserService {
    override fun getFact(id: UserID): Fact {
        var aux: Fact? = null
        userRepository.getUserById(id) { user ->
            if (user == null) {
                userClient.getUser(id) { userFromClient ->
                    userRepository.insertUser(userFromClient) {}
                    factClient.get(userFromClient) { fact ->
                        factRepository.insertFact(fact) {}
                        aux = fact
                    }
                }
            } else {
                factRepository.getFactByUserId(id) { fact ->
                    if (fact == null) {
                        factClient.get(user) { factFromClient ->
                            factRepository.insertFact(factFromClient) {}
                            aux = factFromClient
                        }
                    } else {
                        aux = fact
                    }
                }
            }
        }
        while (aux == null) {
            Thread.sleep(2)
        }
        return aux!!
    }
}


fun main(args: Array<String>) {
    fun execute(userService: UserService, id: Int) {
        val (fact, time) = inTime {
            userService.getFact(id)
        }
        println("fact = $fact")
        println("time = $time ms.")
    }

    val userClient = MockUserClient()
    val callbackUserClient = CallbackUserClient(userClient)
    val factClient = MockFactClient()
    val callBackFactClient = CallbackFactClient(factClient)
    val userRepository = MockUserRepository()
    val callbackUserRepository = CallBackUserRepository(userRepository)
    val factRepository = MockFactRepository()
    val callBackFactRepository = CallBackFactRepository(factRepository)

    val callBackUserService = CallBackUserService(
        userClient = callbackUserClient,
        factClient = callBackFactClient,
        userRepository = callbackUserRepository,
        factRepository = callBackFactRepository
    )

    execute(callBackUserService, 1)
    execute(callBackUserService, 2)
    execute(callBackUserService, 1)
    execute(callBackUserService, 2)
    execute(callBackUserService, 3)
    execute(callBackUserService, 4)
    execute(callBackUserService, 5)
    execute(callBackUserService, 10)
    execute(callBackUserService, 100)
}

和 2.使用协程:

package com.freesoft.functional.coroutines

import kotlinx.coroutines.*

class CoroutinesUserService(
    private val userClient: UserClient,
    private val factClient: FactClient,
    private val userRepository: UserRepository,
    private val factRepository: FactRepository
) : UserService {
    override fun getFact(id: UserID): Fact = runBlocking {
        val user = async { userRepository.getUserById(id) }.await()
        if (user == null) {
            val userFromService = async { userClient.getUser(id) }.await()
            launch { userRepository.insertUser(userFromService) }
            getFact(userFromService)
        } else {
            async {
                factRepository.getFactByUserID(id) ?: getFact(user)
            }.await()
        }
    }

    private suspend fun getFact(user: User): Fact {
        val fact: Deferred<Fact> = withContext(Dispatchers.Default) {
            async { factClient.getFact(user) }
        }
        coroutineScope {
            launch { factRepository.insertFact(fact.await()) }
        }
        return fact.await()

    }
}

fun main(args: Array<String>) {
    fun execute(userService: UserService, id: Int) {
        val (fact, time) = inTime {
            userService.getFact(id)
        }
        println("fact = $fact")
        println("time = $time ms.")
    }

    val userClient = MockUserClient()
    val factClient = MockFactClient()
    val userRepository = MockUserRepository()
    val factRepository = MockFactRepository()

    val coroutinesUserService = CoroutinesUserService(userClient, factClient, userRepository, factRepository)

    execute(coroutinesUserService, 1)
    execute(coroutinesUserService, 2)
    execute(coroutinesUserService, 1)
    execute(coroutinesUserService, 2)
    execute(coroutinesUserService, 3)
    execute(coroutinesUserService, 4)
    execute(coroutinesUserService, 5)
    execute(coroutinesUserService, 10)
    execute(coroutinesUserService, 100)
}

Here are the mocks that I'm using `UserClient,FactClient,UserRepository and FactRepository`:

class MockUserClient : UserClient {
    override fun getUser(id: UserID): User {
        println("MockUserClient.getUser")
        Thread.sleep(500)
        return User(id, "Foo", "Bar", Gender.FEMALE)
    }
}

class MockFactClient : FactClient {
    override fun getFact(user: User): Fact {
        println("MockFactClient.getFact")
        Thread.sleep(500)
        return Fact(Random().nextInt(), "FACT ${user.firstName}, ${user.lastName}", user)
    }
}

class MockUserRepository : UserRepository {
    private val users = hashMapOf<UserID, User>()

    override fun getUserById(id: UserID): User? {
        println("MockUserRepository.getUserById")
        Thread.sleep(200)
        return users[id]
    }

    override fun insertUser(user: User) {
        println("MockUserRepository.insertUser")
        Thread.sleep(200)
        users[user.id] = user
    }
}

class MockFactRepository : FactRepository {
    private val facts = hashMapOf<UserID, Fact>()

    override fun getFactByUserID(userID: UserID): Fact? {
        println("MockFactRepository.getFactByUserId")
        Thread.sleep(200)
        return facts[userID]
    }

    override fun insertFact(fact: Fact) {
        println("MockFactRepository.insertFact")
        Thread.sleep(200)
        facts[fact.user?.id ?: 0] = fact
    }
}

我的问题是:为什么用协程实现的时间更昂贵,即使模拟对象的请求总和应该在1.2秒左右?在回调实现中,我收到了正确的执行时间(1.2秒),但在协程实现中,我收到了大约1.4秒的执行时间。

1 个答案:

答案 0 :(得分:2)

这里有两个误解:

  1. 协程比线程快
  2. 您实际上在有效地使用协程

让我们从第一个开始。
协程并不比线程快。但是,它们并发性更高。这意味着,如果您有成千上万的协同程序,那么您仍然会没事的。如果您有成千上万的线程-您将耗尽内存。

但是您的测试完全没有侵略性:

execute(callBackUserService, 1)
execute(callBackUserService, 2)
execute(callBackUserService, 1)
execute(callBackUserService, 2)
execute(callBackUserService, 3)
execute(callBackUserService, 4)
execute(callBackUserService, 5)
execute(callBackUserService, 10)
execute(callBackUserService, 100)

即使您为每个execute启动6个线程,也不会导致足够的开销。做类似

 repeat(100_000) {
     execute(callBackUserService, 100)
 }

您会看到区别。

不让我们谈第二点。

override fun getFact(id: UserID): Fact = runBlocking {
    val user = async { userRepository.getUserById(id) }.await()
    ...
}

runBlocking的作用是阻止您的调用执行上下文。由于您使用的是main线程,因此只有一个线程正在运行。而且,通过像使用async {}.await()一样,您不会从任何并发中受益,因为您在线程上运行任务,然后立即将其阻塞。

一旦您克服了这两个并发问题,第三个问题就在等着您:模拟中的Thread.sleep(200)

您可能期望的是每个协程将被阻止200毫秒。实际上,由于所有协程共享相同的执行池,因此它们被阻塞了200ms。如果要使用协同程序测试模拟,则必须使用delay(),否则,您将不会进行任何测试。