协程调度器详解

协程和线程的差异

  • 线程的目的是提高CPU资源使用率, 使多个任务得以并行的运行,是为了服务于机器的.
  • 协程的目的是为了让多个任务之间更好的协作,主要体现在代码逻辑上,是为了服务开发者 (能提升资源的利用率, 但并不是原始目的)

协程的核心竞争力

简化异步并发任务。

协程上下文 CoroutineContext

  • 协程总是运行在一些以 CoroutineContext 类型为代表的上下文中 ,协程上下文是各种不同元素的集合
  • 集合内部的元素Element是根据key去对应(Map特点),但是不允许重复(Set特点)
  • Element之间可以通过+号进行组合
  • Element有如下四类,共同组成了CoroutineContext
    • Job:协程的唯一标识,用来控制协程的生命周期(newactivecompletingcompletedcancellingcancelled)
    • CoroutineDispatcher:指定协程运行的线程(IODefaultMainUnconfined)
    • CoroutineName: 指定协程的名称,默认为coroutine
    • CoroutineExceptionHandler: 指定协程的异常处理器,用来处理未捕获的异常

它们的关系如图所示:

[图片上传失败...(image-9c6306-1637655771397)]

协程切换线程源码分析

我们在协程体内,可能通过withContextlaunch方法简单便捷的切换线程,用同步的方式写异步代码,这也是kotin协程的主要优势之一

示例:

private fun testDispatchers() = runBlocking {

    Log.d(TAG, "main                 : I'm working in thread ${Thread.currentThread().name}")

    launch(Dispatchers.Default) {
        Log.d(TAG, "launch Default       : I'm working in thread ${Thread.currentThread().name}")
    }

    withContext(Dispatchers.Default) {
        Log.d(TAG, "withContext Default  : I'm working in thread ${Thread.currentThread().name} ")
    }
}

输出结果为:

[图片上传失败...(image-e89054-1637655771397)]
从输出结果可以看出,调用Dispatch.Default会由主线程切换到DefaultDispatcher-worker-3线程,而且launchwithContext切换的线程是相同的。

launch方法解析

协程的发起方式如下

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    //创建协程上下文Context
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    //创建一个独立协程并启动
    coroutine.start(start, coroutine, block)
    return coroutine
}

launch方法主要作用:
1、是创建新的上下文Context
2、创建并启动协程

组合一个新的Context

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    //根据传入的Context 组合成新的上下文
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    //如果发起的时候没有传入调度器,则使用默认的Default
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

从上述方法中能够得出,此方法主要是
1、将launch方法传入的contextCoroutineScope中的context组合起来
2、若combined中没传入一个调度器 ,则会默认使用Dispatchers.Default调度器

创建一个独立协程Coroutine

val coroutine = if (start.isLazy)
    LazyStandaloneCoroutine(newContext, block) else
    StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)

//继承抽象协程类
private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
   //省略......
}

//AbstractCoroutine类核心源码
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T){
    initParentJob()
    start(block, receiver, this)
}

// CoroutineStart类核心源码
public operator fun <T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>)
    when (this) {
        //launch 默认为DEFAULT
        CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
        CoroutineStart.ATOMIC -> block.startCoroutine(completion)
        CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
        CoroutineStart.LAZY -> Unit // will start lazily
    }

创建一个协程体 Continuation

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion)
            //如果需要则进行拦截处理
            .intercepted()
            //调用 resumeWith 方法      
            .resumeCancellableWith(Result.success(Unit))
    }

调用createCoroutineUnintercepted,会把我们的协程体即suspend block转换成Continuation

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

//ContinuationImpl类核心源码
public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }     

//CoroutineDispatcher类核心源码
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
      DispatchedContinuation(this, continuation)           

从上述方法可以得出
1.interepted是个扩展方法,最后会调用到ContinuationImpl.intercepted方法
2.在intercepted会利用CoroutineContext,获取当前的调度器
3.当前调度器是CoroutineDispatcher,最终会返回一个DispatchedContinuation,我们也是利用它来实现线程切换的

调度处理

//DispatchedContinuation
public fun <T> Continuation<T>.resumeCancellableWith(result: Result<T>) = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result)
    else -> resumeWith(result)
}


@Suppress("NOTHING_TO_INLINE")
inline fun resumeCancellableWith(result: Result<T>) {
    val state = result.toState()
    //判断是否需要切换线程
    if (dispatcher.isDispatchNeeded(context)) {
        _state = state
        resumeMode = MODE_CANCELLABLE
        //调用器进行切换线程
        dispatcher.dispatch(context, this)
    } else {
        //Unconfined,会执行该方法
        executeUnconfined(state, MODE_CANCELLABLE) {
            if (!resumeCancelled()) {
                resumeUndispatchedWith(result)
            }
        }
    }
}

上述分析可得出
1、判断是否需要切换线程,如果需要则调用dispatcher.dispatch()方法进行切换线程
2、如果不需要切换线程 ,则直接在原有线程执行。

withContext方法解析

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->

    //创建新的content
    val oldContext = uCont.context
    val newContext = oldContext + context
    
    ......
    
    //创建新的调度协程
    val coroutine = DispatchedCoroutine(newContext, uCont)
    //初始化父类Job
    coroutine.initParentJob()
    //开始一个可以取消的协程
    block.startCoroutineCancellable(coroutine, coroutine)
    coroutine.getResult()
}

private class DispatchedCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {

    //在complete时会会回调
    override fun afterCompletion(state: Any?) {
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
        //uCont就是父协程,context仍是老版context,因此可以切换回原来的线程上
        uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
    }
}

从上述方法可以得出,调用withContext方法最终也是调用uCont.intercepted().resumeCancellableWith方法与launch方法最后切换线程是相同的,
这里也说明了上面输出结果,为什么二者调用同一调度器切换的线程是相同的。
也有不相同的时候,就是当线程DefaultDispatcher-worker-1还没创建成功的时候,withContext已经需要切换线程时,会再创建一个新的线程,如下图所示

[图片上传失败...(image-3e02ba-1637655771397)]

其切换线程的流程图为:

[图片上传失败...(image-d7223e-1637655771397)]

CoroutineDispatcher 作用

  • 用于指定协程的运行线程
  • kotlin已经内置了CoroutineDispatcher的4个实现,分别为 DispatchersDefaultIOMainUnconfined字段

public actual object Dispatchers {

    @JvmStatic
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
    
    @JvmStatic
    public val IO: CoroutineDispatcher = DefaultScheduler.IO
    
    @JvmStatic
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    
    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}

[图片上传失败...(image-adf112-1637655771397)]

Dispatchers.Default

Default根据useCoroutinesScheduler属性(默认为true) 去获取对应的线程池

  • DefaultScheduler :Kotlin内部自己实现的线程池逻辑
  • CommonPoolJava类库中的Executor实现的线程池逻辑
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
    if (useCoroutinesScheduler) DefaultScheduler else CommonPool
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    .....
}

open class ExperimentalCoroutineDispatcher(
    private val corePoolSize: Int,
    private val maxPoolSize: Int,
    private val idleWorkerKeepAliveNs: Long,
    private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
    constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE,
        schedulerName: String = DEFAULT_SCHEDULER_NAME
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)

    ......
}
//java类库中的Executor实现线程池逻辑
internal object CommonPool : ExecutorCoroutineDispatcher() {}

如果想使用java类库中的线程池该如何使用呢?也就是修改useCoroutinesScheduler属性为false

internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"

internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
    when (value) {
        null, "", "on" -> true
        "off" -> false
        else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
    }
}

internal actual fun systemProp(
    propertyName: String
): String? =
    try {
       //获取系统属性
        System.getProperty(propertyName)
    } catch (e: SecurityException) {
        null
    }

从源码中可以看到,使用过获取系统属性拿到的值, 那我们就可以通过修改系统属性 去改变useCoroutinesScheduler的值,
具体修改方法为

 val properties = Properties()
 properties["kotlinx.coroutines.scheduler"] = "off"
 System.setProperties(properties)

DefaultScheduler的主要实现都在其父类 ExperimentalCoroutineDispatcher

open class ExperimentalCoroutineDispatcher(
    private val corePoolSize: Int,
    private val maxPoolSize: Int,
    private val idleWorkerKeepAliveNs: Long,
    private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
    public constructor(
        corePoolSize: Int = CORE_POOL_SIZE,
        maxPoolSize: Int = MAX_POOL_SIZE,
        schedulerName: String = DEFAULT_SCHEDULER_NAME
    ) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
    
    //省略......
    
    //创建CoroutineScheduler实例
    private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
    
    override val executor: Executorget() = coroutineScheduler

    //此方法也就是上文说到切换线程的方法
    override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
        try {
            //dispatch方法委托到CoroutineScheduler的dispatch方法
            coroutineScheduler.dispatch(block)
        } catch (e: RejectedExecutionException) {
            ....
        }

    //省略......
    
    //实现请求阻塞,执行IO密集型任务
    public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
        require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
        return LimitingDispatcher(this, parallelism, null, TASK_PROBABLY_BLOCKING)
    }
    //实现并发数量限制,执行CPU密集型任务
    public fun limited(parallelism: Int): CoroutineDispatcher {
        require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
        require(parallelism <= corePoolSize) { "Expected parallelism level lesser than core pool size ($corePoolSize), but have $parallelism" }
        return LimitingDispatcher(this, parallelism, null, TASK_NON_BLOCKING)
    }
    
   //省略......
}

从上文代码可以提炼出
1、在ExperimentalCoroutineDispatcher类中创建协程调度线程池coroutineScheduler,通过该线程池来管理线程。
2、该类中的dispatch()方法,在协程切换线程中 dispatcher.dispatch(context, this) 调用。
3、其中 blocking()方法是执行IO密集型任务,limited()方法执行CPU密集型任务,
实现请求数量限制是调用LimitingDispatcher 类,其类实现为

private class LimitingDispatcher(
    private val dispatcher: ExperimentalCoroutineDispatcher,
    private val parallelism: Int,
    private val name: String?,
    override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
    //同步阻塞队列
    private val queue = ConcurrentLinkedQueue<Runnable>()
    //cas计数
    private val inFlightTasks = atomic(0)
    
    override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)

    private fun dispatch(block: Runnable, tailDispatch: Boolean) {
        var taskToSchedule = block
        while (true) {

            if (inFlight <= parallelism) {
                //LimitingDispatcher的dispatch方法委托给了DefaultScheduler的dispatchWithContext方法
                dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
                return
            }
            ......
        }
    }
}

Dispatchers.IO

先看下Dispatchers.IO 的定义

    @JvmStatic
    public val IO: CoroutineDispatcher = DefaultScheduler.IO
    
    
    Internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))

IODefaultScheduler中的实现 是调用blacking()方法,而blacking()方法最终实现是LimitingDispatcher类,
所以 从源码可以看出 Dispatchers.DefaultIO 是在同一个线程中运行的,也就是共用相同的线程池。

DefaultIO 都是共享CoroutineScheduler线程池 ,kotlin内部实现了一套线程池两种调度策略,主要是通过dispatch方法中的Mode区分的

Type Mode
Default NON_BLOCKING
IO PROBABLY_BLOCKING
internal enum class TaskMode {

    //执行CPU密集型任务
    NON_BLOCKING,

    //执行IO密集型任务
    PROBABLY_BLOCKING,
}

//CoroutineScheduler类核心源码
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
      
      ......
      
     if (task.mode == TaskMode.NON_BLOCKING) {
            signalCpuWork() //Dispatchers.Default
     } else {
            signalBlockingWork() // Dispatchers.IO
     }
}

从上述代码中可以提炼出的是:
1、signalCpuWork()方法处理CPU密集任务,在该方法中根据CPU密集型任务处理策略,创建并管理线程以及执行任务
2、signalBlockingWork()方法处理IO密集任务,在该方法中根据IO密集型任务处理策略,创建并管理线程以及执行任务

其处理策略如下图所示:
[图片上传失败...(image-e24d8-1637655771397)]

Dispatchers.Unconfined

任务执行在默认的启动线程。之后由调用resume的线程决定恢复协程的线程

internal object Unconfined : CoroutineDispatcher() {
    //为false为不需要dispatch
    override fun isDispatchNeeded(context: CoroutineContext): Boolean = false

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        // 只有当调用yield方法时,Unconfined的dispatch方法才会被调用
        // yield() 表示当前协程让出自己所在的线程给其他协程运行
        val yieldContext = context[YieldContext]
        if (yieldContext != null) {
            yieldContext.dispatcherWasUnconfined = true
            return
        }
        throw UnsupportedOperationException("Dispatchers.Unconfined.dispatch function can only be used by the yield function. " +
            "If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
            "isDispatchNeeded and dispatch calls.")
    }
}

每一个协程都有对应的Continuation实例,其中的resumeWith用于协程的恢复,存在于DispatchedContinuation,重点看resumeWith的实现以及类委托

internal class DispatchedContinuation<in T>(
    @JvmField val dispatcher: CoroutineDispatcher,
    @JvmField val continuation: Continuation<T>//协程suspend挂起方法产生的Continuation
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
    .....
    override fun resumeWith(result: Result<T>) {
        val context = continuation.context
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_ATOMIC
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_ATOMIC) {
                withCoroutineContext(this.context, countOrElement) {
                    continuation.resumeWith(result)
                }
            }
        }
    }
    ....
}

通过isDispatchNeeded(是否需要dispatchUnconfined=falsedefaultIO=true)判断做不同处理

  • true:调用协程的CoroutineDispatcherdispatch方法
  • false:调用executeUnconfined方法
private inline fun DispatchedContinuation<*>.executeUnconfined(
    contState: Any?, mode: Int, doYield: Boolean = false,
    block: () -> Unit
): Boolean {
    assert { mode != MODE_UNINITIALIZED }
    val eventLoop = ThreadLocalEventLoop.eventLoop
    if (doYield && eventLoop.isUnconfinedQueueEmpty) return false
    return if (eventLoop.isUnconfinedLoopActive) {
        _state = contState
        resumeMode = mode
        eventLoop.dispatchUnconfined(this)
        true
    } else {
        runUnconfinedEventLoop(eventLoop, block = block)
        false
    }
}

threadlocal中取出eventLoopeventLoop和当前线程相关),判断是否在执行Unconfined任务

  1. 如果在执行则调用EventLoopdispatchUnconfined方法把Unconfined任务放进EventLoop
  2. 如果没有在执行则直接执行
internal inline fun DispatchedTask<*>.runUnconfinedEventLoop(
    eventLoop: EventLoop,
    block: () -> Unit
) {
    eventLoop.incrementUseCount(unconfined = true)
    try {
        block()
        while (true) {
            if (!eventLoop.processUnconfinedEvent()) break
        }
    } catch (e: Throwable) {
        handleFatalException(e, null)
    } finally {
        eventLoop.decrementUseCount(unconfined = true)
    }
}

  1. 执行block()代码块,即上文提到的resumeWith()
  2. 调用processUnconfinedEvent()方法实现执行剩余的Unconfined任务,直到全部执行完毕跳出循环
    EventLoopCoroutineDispatcher的一个子类
internal abstract class EventLoop : CoroutineDispatcher() {
    .....
    //双端队列实现存放Unconfined任务
    private var unconfinedQueue: ArrayQueue<DispatchedTask<*>>? = null
    //从队列的头部移出Unconfined任务执行
    public fun processUnconfinedEvent(): Boolean {
        val queue = unconfinedQueue ?: return false
        val task = queue.removeFirstOrNull() ?: return false
        task.run()
        return true
    }
    //把Unconfined任务放进队列的尾部
    public fun dispatchUnconfined(task: DispatchedTask<*>) {
        val queue = unconfinedQueue ?:
            ArrayQueue<DispatchedTask<*>>().also { unconfinedQueue = it }
        queue.addLast(task)
    }
    .....
}

内部通过双端队列实现存放Unconfined任务

  1. EventLoopdispatchUnconfined方法用于把Unconfined任务放进队列的尾部
  2. processUnconfinedEvent方法用于从队列的头部移出Unconfined任务执行

Dispatchers.Main

kotlinJVM上的实现 Android就需要引入kotlinx-coroutines-android库,它里面有Android对应的Dispatchers.Main实现,

   public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
   
     @JvmField
    val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()

    private fun loadMainDispatcher(): MainCoroutineDispatcher {
        return try {
            val factories = if (FAST_SERVICE_LOADER_ENABLED) {
                FastServiceLoader.loadMainDispatcherFactory()
            } else {
                ServiceLoader.load(
                        MainDispatcherFactory::class.java,
                        MainDispatcherFactory::class.java.classLoader
                ).iterator().asSequence().toList()
            }
            factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)
                ?: MissingMainCoroutineDispatcher(null)
        } catch (e: Throwable) {
            // Service loader can throw an exception as well
            MissingMainCoroutineDispatcher(e)
        }
    }
    
    internal fun loadMainDispatcherFactory(): List<MainDispatcherFactory> {
        val clz = MainDispatcherFactory::class.java
        if (!ANDROID_DETECTED) {
            return load(clz, clz.classLoader)
        }

        return try {
            val result = ArrayList<MainDispatcherFactory>(2)
            createInstanceOf(clz, "kotlinx.coroutines.android.AndroidDispatcherFactory")?.apply { result.add(this) }
            createInstanceOf(clz, "kotlinx.coroutines.test.internal.TestMainDispatcherFactory")?.apply { result.add(this) }
            result
        } catch (e: Throwable) {
            // Fallback to the regular SL in case of any unexpected exception
            load(clz, clz.classLoader)
        }
    }

从上文代码中主要功能是通过反射获取AndroidDispatcherFactory 然后根据加载的优先级 去创建Dispatcher

internal class AndroidDispatcherFactory : MainDispatcherFactory {

    override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
        HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")

    override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"

    override val loadPriority: Int
        get() = Int.MAX_VALUE / 2
}
internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
   
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)

   ......

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        handler.post(block)
    }

    ......
}

从上文代码中可以提炼出以下信息:createDispatcher调用HandlerContext类,通过调用Looper.getMainLooper()获取handler ,最终通过handler来实现在主线程中运行.
可以得出Dispatchers.Main其实就是把任务通过Handler运行在Android主线程中的。

总结

1、Dispatchers.Default,切换线程执行CPU密集型任务
2、Dispatchers.IO,切换线程执行IO密集型任务
3、Dispatchers.Unconfined,任务执行在默认的启动线程
4、Dispatchers.Main,切换线程到主线程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容