《深入理解Spark:核心思想与源码分析》一书前言的内容请看链接《深入理解SPARK:核心思想与源码分析》一书正式出版上市

《深入理解Spark:核心思想与源码分析》一书第一章的内容请看链接《第1章 环境准备》

《深入理解Spark:核心思想与源码分析》一书第二章的内容请看链接《第2章 SPARK设计理念与基本架构》

由于本书的第3章内容较多,所以打算分别开辟四篇随笔分别展现。

《深入理解Spark:核心思想与源码分析》一书第三章第一部分的内容请看链接《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(伯篇)》

《深入理解Spark:核心思想与源码分析》一书第三章第二部分的内容请看链接《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(仲篇)》

本文展现第3章第三部分的内容:

3.8 TaskScheduler的启动

  3.7节介绍了任务调度器TaskScheduler的创建,要想TaskScheduler发挥作用,必须要启动它,代码如下。

  1. taskScheduler.start()

TaskScheduler在启动的时候,实际调用了backend的start方法。

  1. override def start() {
  2.  
  3. backend.start()
  4.  
  5. }

以LocalBackend为例,启动LocalBackend时向actorSystem注册了LocalActor,见代码清单3-30所示(在《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(中)》一文)。

3.8.1 创建LocalActor

  创建LocalActor的过程主要是构建本地的Executor,见代码清单3-36。

代码清单3-36         LocalActor的实现

  1. private[spark] class LocalActor(scheduler: TaskSchedulerImpl, executorBackend: LocalBackend,
  2.  
  3. private val totalCores: Int) extends Actor with ActorLogReceive with Logging {
  4.  
  5. import context.dispatcher // to use Akka's scheduler.scheduleOnce()
  6.  
  7. private var freeCores = totalCores
  8.  
  9. private val localExecutorId = SparkContext.DRIVER_IDENTIFIER
  10.  
  11. private val localExecutorHostname = "localhost"
  12.  
  13. val executor = new Executor(
  14.  
  15. localExecutorId, localExecutorHostname, scheduler.conf.getAll, totalCores, isLocal = true)
  16.  
  17. override def receiveWithLogging = {
  18.  
  19. case ReviveOffers =>
  20.  
  21. reviveOffers()
  22.  
  23. case StatusUpdate(taskId, state, serializedData) =>
  24.  
  25. scheduler.statusUpdate(taskId, state, serializedData)
  26.  
  27. if (TaskState.isFinished(state)) {
  28.  
  29. freeCores += scheduler.CPUS_PER_TASK
  30.  
  31. reviveOffers()
  32.  
  33. }
  34.  
  35. case KillTask(taskId, interruptThread) =>
  36.  
  37. executor.killTask(taskId, interruptThread)
  38.  
  39. case StopExecutor =>
  40.  
  41. executor.stop()
  42.  
  43. }
  44.  
  45. }

Executor的构建,见代码清单3-37,主要包括以下步骤:

1) 创建并注册ExecutorSource。ExecutorSource是做什么的呢?笔者将在3.10.2节详细介绍。

2) 获取SparkEnv。如果是非local模式,Worker上的CoarseGrainedExecutorBackend向Driver上的CoarseGrainedExecutorBackend注册Executor时,则需要新建SparkEnv。可以修改属性spark.executor.port(默认为0,表示随机生成)来配置Executor中的ActorSystem的端口号。

3) 创建并注册ExecutorActor。ExecutorActor负责接受发送给Executor的消息。

4) urlClassLoader的创建。为什么需要创建这个ClassLoader?在非local模式中,Driver或者Worker上都会有多个Executor,每个Executor都设置自身的urlClassLoader,用于加载任务上传的jar包中的类,有效对任务的类加载环境进行隔离。

5) 创建Executor执行TaskRunner任务(TaskRunner将在5.5节介绍)的线程池。此线程池是通过调用Utils.newDaemonCachedThreadPool创建的,具体实现请参阅附录A。

6) 启动Executor的心跳线程。此线程用于向Driver发送心跳。

此外,还包括Akka发送消息的帧大小(10485760字节)、结果总大小的字节限制(1073741824字节)、正在运行的task的列表、设置serializer的默认ClassLoader为创建的ClassLoader等。

代码清单3-37         Executor的构建

  1. val executorSource = new ExecutorSource(this, executorId)
  2.  
  3. private val env = {
  4.  
  5. if (!isLocal) {
  6.  
  7. val port = conf.getInt("spark.executor.port", 0)
  8.  
  9. val _env = SparkEnv.createExecutorEnv(
  10.  
  11. conf, executorId, executorHostname, port, numCores, isLocal, actorSystem)
  12.  
  13. SparkEnv.set(_env)
  14.  
  15. _env.metricsSystem.registerSource(executorSource)
  16.  
  17. _env.blockManager.initialize(conf.getAppId)
  18.  
  19. _env
  20.  
  21. } else {
  22.  
  23. SparkEnv.get
  24.  
  25. }
  26.  
  27. }
  28.  
  29. private val executorActor = env.actorSystem.actorOf(
  30.  
  31. Props(new ExecutorActor(executorId)), "ExecutorActor")
  32.  
  33. private val urlClassLoader = createClassLoader()
  34.  
  35. private val replClassLoader = addReplClassLoaderIfNeeded(urlClassLoader)
  36.  
  37. env.serializer.setDefaultClassLoader(urlClassLoader)
  38.  
  39. private val akkaFrameSize = AkkaUtils.maxFrameSizeBytes(conf)
  40.  
  41. private val maxResultSize = Utils.getMaxResultSize(conf)
  42.  
  43. val threadPool = Utils.newDaemonCachedThreadPool("Executor task launch worker")
  44.  
  45. private val runningTasks = new ConcurrentHashMap[Long, TaskRunner]
  46.  
  47. startDriverHeartbeater()

3.8.2 ExecutorSource的创建与注册

  ExecutorSource用于测量系统。通过metricRegistry的register方法注册计量,这些计量信息包括threadpool.activeTasks、threadpool.completeTasks、threadpool.currentPool_size、threadpool.maxPool_size、filesystem.hdfs.write_bytes、filesystem.hdfs.read_ops、filesystem.file.write_bytes、filesystem.hdfs.largeRead_ops、filesystem.hdfs.write_ops等,ExecutorSource的实现见代码清单3-38。Metric接口的具体实现,参考附录D。

代码清单3-38         ExecutorSource的实现

  1. private[spark] class ExecutorSource(val executor: Executor, executorId: String) extends Source {
  2.  
  3. private def fileStats(scheme: String) : Option[FileSystem.Statistics] =
  4.  
  5. FileSystem.getAllStatistics().filter(s => s.getScheme.equals(scheme)).headOption
  6.  
  7. private def registerFileSystemStat[T](
  8.  
  9. scheme: String, name: String, f: FileSystem.Statistics => T, defaultValue: T) = {
  10.  
  11. metricRegistry.register(MetricRegistry.name("filesystem", scheme, name), new Gauge[T] {
  12.  
  13. override def getValue: T = fileStats(scheme).map(f).getOrElse(defaultValue)
  14.  
  15. })
  16.  
  17. }
  18.  
  19. override val metricRegistry = new MetricRegistry()
  20.  
  21. override val sourceName = "executor"
  22.  
  23. metricRegistry.register(MetricRegistry.name("threadpool", "activeTasks"), new Gauge[Int] {
  24.  
  25. override def getValue: Int = executor.threadPool.getActiveCount()
  26.  
  27. })
  28.  
  29. metricRegistry.register(MetricRegistry.name("threadpool", "completeTasks"), new Gauge[Long] {
  30.  
  31. override def getValue: Long = executor.threadPool.getCompletedTaskCount()
  32.  
  33. })
  34.  
  35. metricRegistry.register(MetricRegistry.name("threadpool", "currentPool_size"), new Gauge[Int] {
  36.  
  37. override def getValue: Int = executor.threadPool.getPoolSize()
  38.  
  39. })
  40.  
  41. metricRegistry.register(MetricRegistry.name("threadpool", "maxPool_size"), new Gauge[Int] {
  42.  
  43. override def getValue: Int = executor.threadPool.getMaximumPoolSize()
  44.  
  45. })
  46.  
  47. // Gauge for file system stats of this executor
  48.  
  49. for (scheme <- Array("hdfs", "file")) {
  50.  
  51. registerFileSystemStat(scheme, "read_bytes", _.getBytesRead(), 0L)
  52.  
  53. registerFileSystemStat(scheme, "write_bytes", _.getBytesWritten(), 0L)
  54.  
  55. registerFileSystemStat(scheme, "read_ops", _.getReadOps(), 0)
  56.  
  57. registerFileSystemStat(scheme, "largeRead_ops", _.getLargeReadOps(), 0)
  58.  
  59. registerFileSystemStat(scheme, "write_ops", _.getWriteOps(), 0)
  60.  
  61. }
  62.  
  63. } 

创建完ExecutorSource后,调用MetricsSystem的registerSource方法将ExecutorSource注册到MetricsSystem。registerSource方法使用MetricRegistry的register方法,将Source注册到MetricRegistry,见代码清单3-39。关于MetricRegistry,具体参阅附录D。

代码清单3-39         MetricsSystem注册Source的实现

  1. def registerSource(source: Source) {
  2.  
  3. sources += source
  4.  
  5. try {
  6.  
  7. val regName = buildRegistryName(source)
  8.  
  9. registry.register(regName, source.metricRegistry)
  10.  
  11. } catch {
  12.  
  13. case e: IllegalArgumentException => logInfo("Metrics already registered", e)
  14.  
  15. }
  16.  
  17. } 

3.8.3 ExecutorActor的构建与注册

  ExecutorActor很简单,当接收到SparkUI发来的消息时,将所有线程的栈信息发送回去,代码实现如下。

  1. override def receiveWithLogging = {
  2.  
  3. case TriggerThreadDump =>
  4.  
  5. sender ! Utils.getThreadDump()
  6.  
  7. }

3.8.4 Spark自身ClassLoader的创建

  获取要创建的ClassLoader的父加载器currentLoader,然后根据currentJars生成URL数组,spark.files.userClassPathFirst属性指定加载类时是否先从用户的classpath下加载,最后创建ExecutorURLClassLoader或者ChildExecutorURLClassLoader,见代码清单3-40。

代码清单3-40         Spark自身ClassLoader的创建

  1. private def createClassLoader(): MutableURLClassLoader = {
  2.  
  3. val currentLoader = Utils.getContextOrSparkClassLoader
  4.  
  5. val urls = currentJars.keySet.map { uri =>
  6.  
  7. new File(uri.split("/").last).toURI.toURL
  8.  
  9. }.toArray
  10.  
  11. val userClassPathFirst = conf.getBoolean("spark.files.userClassPathFirst", false)
  12.  
  13. userClassPathFirst match {
  14.  
  15. case true => new ChildExecutorURLClassLoader(urls, currentLoader)
  16.  
  17. case false => new ExecutorURLClassLoader(urls, currentLoader)
  18.  
  19. }
  20.  
  21. } 

Utils.getContextOrSparkClassLoader的实现见附录A。ExecutorURLClassLoader或者ChildExecutorURLClassLoader实际上都继承了URLClassLoader,见代码清单3-41。 

代码清单3-41         ChildExecutorURLClassLoader与ExecutorURLClassLoader的实现

  1. private[spark] class ChildExecutorURLClassLoader(urls: Array[URL], parent: ClassLoader)
  2.  
  3. extends MutableURLClassLoader {
  4.  
  5. private object userClassLoader extends URLClassLoader(urls, null){
  6.  
  7. override def addURL(url: URL) {
  8.  
  9. super.addURL(url)
  10.  
  11. }
  12.  
  13. override def findClass(name: String): Class[_] = {
  14.  
  15. super.findClass(name)
  16.  
  17. }
  18.  
  19. }
  20.  
  21. private val parentClassLoader = new ParentClassLoader(parent)
  22.  
  23. override def findClass(name: String): Class[_] = {
  24.  
  25. try {
  26.  
  27. userClassLoader.findClass(name)
  28.  
  29. } catch {
  30.  
  31. case e: ClassNotFoundException => {
  32.  
  33. parentClassLoader.loadClass(name)
  34.  
  35. }
  36.  
  37. }
  38.  
  39. }
  40.  
  41. def addURL(url: URL) {
  42.  
  43. userClassLoader.addURL(url)
  44.  
  45. }
  46.  
  47. def getURLs() = {
  48.  
  49. userClassLoader.getURLs()
  50.  
  51. }
  52.  
  53. }
  54.  
  55. private[spark] class ExecutorURLClassLoader(urls: Array[URL], parent: ClassLoader)
  56.  
  57. extends URLClassLoader(urls, parent) with MutableURLClassLoader {
  58.  
  59. override def addURL(url: URL) {
  60.  
  61. super.addURL(url)
  62.  
  63. }
  64.  
  65. }

如果需要REPL交互,还会调用addReplClassLoaderIfNeeded创建replClassLoader,见代码清单3-42。

代码清单3-42         addReplClassLoaderIfNeeded的实现

  1. private def addReplClassLoaderIfNeeded(parent: ClassLoader): ClassLoader = {
  2.  
  3. val classUri = conf.get("spark.repl.class.uri", null)
  4.  
  5. if (classUri != null) {
  6.  
  7. logInfo("Using REPL class URI: " + classUri)
  8.  
  9. val userClassPathFirst: java.lang.Boolean =
  10.  
  11. conf.getBoolean("spark.files.userClassPathFirst", false)
  12.  
  13. try {
  14.  
  15. val klass = Class.forName("org.apache.spark.repl.ExecutorClassLoader")
  16.  
  17. .asInstanceOf[Class[_ <: ClassLoader]]
  18.  
  19. val constructor = klass.getConstructor(classOf[SparkConf], classOf[String],
  20.  
  21. classOf[ClassLoader], classOf[Boolean])
  22.  
  23. constructor.newInstance(conf, classUri, parent, userClassPathFirst)
  24.  
  25. } catch {
  26.  
  27. case _: ClassNotFoundException =>
  28.  
  29. logError("Could not find org.apache.spark.repl.ExecutorClassLoader on classpath!")
  30.  
  31. System.exit(1)
  32.  
  33. null
  34.  
  35. }
  36.  
  37. } else {
  38.  
  39. parent
  40.  
  41. }
  42.  
  43. }

3.8.5 启动Executor的心跳线程

  Executor的心跳由startDriverHeartbeater启动,见代码清单3-43。Executor心跳线程的间隔由属性spark.executor.heartbeatInterval配置,默认是10000毫秒。此外,超时时间是30秒,超时重试次数是3次,重试间隔是3000毫秒,使用actorSystem.actorSelection (url)方法查找到匹配的Actor引用, url是akka.tcp://sparkDriver@ $driverHost:$driverPort/user/HeartbeatReceiver,最终创建一个运行过程中,每次会休眠10000到20000毫秒的线程。此线程从runningTasks获取最新的有关Task的测量信息,将其与executorId、blockManagerId封装为Heartbeat消息,向HeartbeatReceiver发送Heartbeat消息。

代码清单3-43         启动Executor的心跳线程

  1. def startDriverHeartbeater() {
  2.  
  3. val interval = conf.getInt("spark.executor.heartbeatInterval", 10000)
  4.  
  5. val timeout = AkkaUtils.lookupTimeout(conf)
  6.  
  7. val retryAttempts = AkkaUtils.numRetries(conf)
  8.  
  9. val retryIntervalMs = AkkaUtils.retryWaitMs(conf)
  10.  
  11. val heartbeatReceiverRef = AkkaUtils.makeDriverRef("HeartbeatReceiver", conf,env.actorSystem)
  12.  
  13. val t = new Thread() {
  14.  
  15. override def run() {
  16.  
  17. // Sleep a random interval so the heartbeats don't end up in sync
  18.  
  19. Thread.sleep(interval + (math.random * interval).asInstanceOf[Int])
  20.  
  21. while (!isStopped) {
  22.  
  23. val tasksMetrics = new ArrayBuffer[(Long, TaskMetrics)]()
  24.  
  25. val curGCTime = gcTime
  26.  
  27. for (taskRunner <- runningTasks.values()) {
  28.  
  29. if (!taskRunner.attemptedTask.isEmpty) {
  30.  
  31. Option(taskRunner.task).flatMap(_.metrics).foreach { metrics =>
  32.  
  33. metrics.updateShuffleReadMetrics
  34.  
  35. metrics.jvmGCTime = curGCTime - taskRunner.startGCTime
  36.  
  37. if (isLocal) {
  38.  
  39. val copiedMetrics = Utils.deserialize[TaskMetrics](Utils.serialize(metrics))
  40.  
  41. tasksMetrics += ((taskRunner.taskId, copiedMetrics))
  42.  
  43. } else {
  44.  
  45. // It will be copied by serialization
  46.  
  47. tasksMetrics += ((taskRunner.taskId, metrics))
  48.  
  49. }
  50.  
  51. }
  52.  
  53. }
  54.  
  55. }
  56.  
  57. val message = Heartbeat(executorId, tasksMetrics.toArray, env.blockManager.blockManagerId)
  58.  
  59. try {
  60.  
  61. val response = AkkaUtils.askWithReply[HeartbeatResponse](message, heartbeatReceiverRef,
  62.  
  63. retryAttempts, retryIntervalMs, timeout)
  64.  
  65. if (response.reregisterBlockManager) {
  66.  
  67. logWarning("Told to re-register on heartbeat")
  68.  
  69. env.blockManager.reregister()
  70.  
  71. }
  72.  
  73. } catch {
  74.  
  75. case NonFatal(t) => logWarning("Issue communicating with driver in heartbeater", t)
  76.  
  77. }
  78.  
  79. Thread.sleep(interval)
  80.  
  81. }
  82.  
  83. }
  84.  
  85. }
  86.  
  87. t.setDaemon(true)
  88.  
  89. t.setName("Driver Heartbeater")
  90.  
  91. t.start()
  92.  
  93. }

这个心跳线程的作用是什么呢?其作用有两个:

q  更新正在处理的任务的测量信息;

q  通知BlockManagerMaster,此Executor上的BlockManager依然活着。

下面对心跳线程的实现详细分析下,读者可以自行选择是否需要阅读。

  初始化TaskSchedulerImpl后会创建心跳接收器HeartbeatReceiver。HeartbeatReceiver接受所有分配给当前Driver Application的Executor的心跳,并将Task、Task计量信息、心跳等交给TaskSchedulerImpl和DAGScheduler作进一步处理。创建心跳接收器的代码如下。

  1. private val heartbeatReceiver = env.actorSystem.actorOf(
  2.  
  3. Props(new HeartbeatReceiver(taskScheduler)), "HeartbeatReceiver")

HeartbeatReceiver在收到心跳消息后,会调用TaskScheduler的executorHeartbeatReceived方法,代码如下。

  1. override def receiveWithLogging = {
  2.  
  3. case Heartbeat(executorId, taskMetrics, blockManagerId) =>
  4.  
  5. val response = HeartbeatResponse(
  6.  
  7. !scheduler.executorHeartbeatReceived(executorId, taskMetrics, blockManagerId))
  8.  
  9. sender ! response
  10.  
  11. }

executorHeartbeatReceived的实现代码如下。

  1. val metricsWithStageIds: Array[(Long, Int, Int, TaskMetrics)] = synchronized {
  2.  
  3. taskMetrics.flatMap { case (id, metrics) =>
  4.  
  5. taskIdToTaskSetId.get(id)
  6.  
  7. .flatMap(activeTaskSets.get)
  8.  
  9. .map(taskSetMgr => (id, taskSetMgr.stageId, taskSetMgr.taskSet.attempt, metrics))
  10.  
  11. }
  12.  
  13. }
  14.  
  15. dagScheduler.executorHeartbeatReceived(execId, metricsWithStageIds, blockManagerId)

这段程序通过遍历taskMetrics,依据taskIdToTaskSetId和activeTaskSets找到TaskSetManager。然后将taskId、TaskSetManager.stageId、TaskSetManager .taskSet.attempt、TaskMetrics封装到Array[(Long, Int, Int, TaskMetrics)]的数组metricsWithStageIds中。最后调用了dagScheduler的executorHeartbeatReceived方法,其实现如下。

  1. listenerBus.post(SparkListenerExecutorMetricsUpdate(execId, taskMetrics))
  2.  
  3. implicit val timeout = Timeout(600 seconds)
  4.  
  5. Await.result(
  6.  
  7. blockManagerMaster.driverActor ? BlockManagerHeartbeat(blockManagerId),
  8.  
  9. timeout.duration).asInstanceOf[Boolean]

dagScheduler将executorId、metricsWithStageIds封装为SparkListenerExecutorMetricsUpdate事件,并post到listenerBus中,此事件用于更新Stage的各种测量数据。最后给BlockManagerMaster持有的BlockManagerMasterActor发送BlockManagerHeartbeat消息。BlockManagerMasterActor在收到消息后会匹配执行heartbeatReceived方法(会在4.3.1节介绍)。heartbeatReceived最终更新BlockManagerMaster对BlockManager最后可见时间(即更新BlockManagerId对应的BlockManagerInfo的_lastSeenMs,见代码清单3-44)。

代码清单3-44         BlockManagerMasterActor的心跳处理

  1. private def heartbeatReceived(blockManagerId: BlockManagerId): Boolean = {
  2.  
  3. if (!blockManagerInfo.contains(blockManagerId)) {
  4.  
  5. blockManagerId.isDriver && !isLocal
  6.  
  7. } else {
  8.  
  9. blockManagerInfo(blockManagerId).updateLastSeenMs()
  10.  
  11. true
  12.  
  13. }
  14.  
  15. }

local模式下Executor的心跳通信过程,可以用图3-3来表示。

图3-3       Executor的心跳通信过程

注意:在非local模式中Executor发送心跳的过程是一样的,主要的区别是Executor进程与Driver不在同一个进程,甚至不在同一个节点上。

 

接下来会初始化块管理器BlockManager,代码如下。

  1. env.blockManager.initialize(applicationId)

具体的初始化过程,请参阅第4章。

未完待续。。。

后记:自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前亚马逊、京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。我开始研究源码时的Spark版本是1.2.0,经过7个多月的研究和出版社近4个月的流程,Spark自身的版本迭代也很快,如今最新已经是1.6.0。目前市面上另外2本源码研究的Spark书籍的版本分别是0.9.0版本和1.2.0版本,看来这些书的作者都与我一样,遇到了这种问题。由于研究和出版都需要时间,所以不能及时跟上Spark的脚步,还请大家见谅。但是Spark核心部分的变化相对还是很少的,如果对版本不是过于追求,依然可以选择本书。

京东(现有满100减30活动):http://item.jd.com/11846120.html

当当:http://product.dangdang.com/23838168.html

TaskScheduler的启动的更多相关文章

  1. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  2. SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  3. spark 源码分析之四 -- TaskScheduler的创建和启动过程

    在 spark 源码分析之二 -- SparkContext 的初始化过程 中,第 14 步 和 16 步分别描述了 TaskScheduler的 初始化 和 启动过程. 话分两头,先说 TaskSc ...

  4. Spark源码剖析 - SparkContext的初始化(七)_TaskScheduler的启动

    7. TaskScheduler的启动 第五节介绍了TaskScheduler的创建,要想TaskScheduler发挥作用,必须要启动它,代码: TaskScheduler在启动的时候,实际调用了b ...

  5. Spark(四十九):Spark On YARN启动流程源码分析(一)

    引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...

  6. Spark On YARN启动流程源码分析(一)

    本文主要参考: a. https://www.cnblogs.com/yy3b2007com/p/10934090.html 0. 说明 a. 关于spark源码会不定期的更新与补充 b. 对于spa ...

  7. 【温故而知新-万花筒】C# 异步编程 逆变 协变 委托 事件 事件参数 迭代 线程、多线程、线程池、后台线程

    额基本脱离了2.0 3.5的时代了.在.net 4.0+ 时代.一切都是辣么简单! 参考文档: http://www.cnblogs.com/linzheng/archive/2012/04/11/2 ...

  8. Spark Core_资源调度与任务调度详述

    转载请标明出处http://www.cnblogs.com/haozhengfei/p/0593214ae0a5395d1411395169eaabfa.html Spark Core_资源调度与任务 ...

  9. Orleans—一些概念

    Orleans-一些概念 这是Orleans系列文章中的一篇.首篇文章在此 这个文章聊一聊Orleans的概念.以下文章大部分翻译自官方教程,还有一些结合实际的应用经验,并对以前文章留下的坑进行填平. ...

随机推荐

  1. Redis中的简单事物以及消息订阅发布

    Redis支持简单的事物,但是没有mysql的Innodb支持的那么的完善 我们接下来看一下Redis和Mysql的事物的一个对比:   MySQL Redis 开启 start transactio ...

  2. mybatis执行批量更新update

    Mybatis的批量插入这里有http://ljhzzyx.blog.163.com/blog/static/38380312201353536375/.目前想批量更新,如果update的值是相同的话 ...

  3. 丰富自己的代码库-SqlServerHelper(Ado)

    设计思路很简单,就是把数据封装为DataTable类,封装了类型转换,使用者可以不必考虑任何类型转换,而使用VC提供的CString即可.封装了按行号查找功能,先看代码 #pragma once #i ...

  4. LinQ的组合+分页

    前台代码: 名称:<asp:TextBox ID="Textname" runat="server"></asp:TextBox> 油耗 ...

  5. leetcode 4. Median of Two Sorted Arrays

    https://leetcode.com/problems/median-of-two-sorted-arrays/ There are two sorted arrays nums1 and num ...

  6. 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记

    转载:http://dsqiu.iteye.com/blog/1669614 第一章 关于对象 使用class封装之后的布局成本: class并没有增加成本,data members直接内含在每一个c ...

  7. C# 采用事务批量插入数据

    首先要构建一个实体类,注意实体类的属性和数据的列要一一对应,否则会报错. public class Animal { public string Name { get; set; } public i ...

  8. 论Segmentation fault

    刚开始学c的时候,最头疼的事情是编译总是通不过,郁闷的要死,只要编译通过了,就兴奋的要死.现在,最头疼的事情是什么呢,编译没问题,但是程序跑的时候会出现Segmentation fault! 这个东西 ...

  9. hive中分号问题

    分号是sql的结束符,在hql中亦如此,但是hive对分号的识别没有那么智能,如下: select concat(';','aa') from lhc limit 1; FAILED: Parse E ...

  10. c++ 中string用c输入输出

    对于string自带的函数 c_str()返回的const char*类型,对于scanf函数不能使用,可以通过如下方法使用 string s; scanf("%s",&* ...