我正在阅读“ Functional Kotlin”这本书,并且刚刚从该书中尝试了一些有关Kotlin中并发编程的示例。我有以下并发代码的实现:
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秒的执行时间。
答案 0 :(得分: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()
,否则,您将不会进行任何测试。