github.com/searKing/golang/go@v1.2.74/container/stream/op/terminal/task.go (about) 1 // Copyright 2020 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package terminal 6 7 import ( 8 "context" 9 "runtime" 10 "sync" 11 12 "github.com/searKing/golang/go/error/exception" 13 "github.com/searKing/golang/go/util/class" 14 "github.com/searKing/golang/go/util/spliterator" 15 ) 16 17 type Task interface { 18 GetSpliterator() spliterator.Spliterator 19 /** 20 * Returns the parent of this task, or null if this task is the root 21 * 22 * @return the parent of this task, or null if this task is the root 23 */ 24 GetParent() Task 25 26 /** 27 * The left child. 28 * null if no children 29 * if non-null rightChild is non-null 30 * 31 * @return the right child 32 */ 33 LeftChild() Task 34 35 SetLeftChild(task Task) 36 37 /** 38 * The right child. 39 * null if no children 40 * if non-null rightChild is non-null 41 * 42 * @return the right child 43 */ 44 RightChild() Task 45 46 SetRightChild(task Task) 47 48 /** 49 * Indicates whether this task is a leaf node. (Only valid after 50 * {@link #compute} has been called on this node). If the node is not a 51 * leaf node, then children will be non-null and numChildren will be 52 * positive. 53 * 54 * @return {@code true} if this task is a leaf node 55 */ 56 IsLeaf() bool 57 58 /** 59 * Indicates whether this task is the root node 60 * 61 * @return {@code true} if this task is the root node. 62 */ 63 IsRoot() bool 64 65 /** 66 * Target leaf size, common to all tasks in a computation 67 * 68 * @return target leaf size. 69 */ 70 TargetSize() int 71 72 /** 73 * Default target of leaf tasks for parallel decomposition. 74 * To allow load balancing, we over-partition, currently to approximately 75 * four tasks per processor, which enables others to help out 76 * if leaf tasks are uneven or some processors are otherwise busy. 77 * 78 * @return the default target size of leaf tasks 79 */ 80 GetLeafTarget() int 81 82 /** 83 * Constructs a new node of type T whose parent is the receiver; must call 84 * the TODOTask(T, Spliterator) constructor with the receiver and the 85 * provided Spliterator. 86 * 87 * @param spliterator {@code Spliterator} describing the subtree rooted at 88 * this node, obtained by splitting the parent {@code Spliterator} 89 * @return newly constructed child node 90 */ 91 MakeChild(spliterator spliterator.Spliterator) Task 92 93 /** 94 * Computes the result associated with a leaf node. Will be called by 95 * {@code compute()} and the result passed to @{code setLocalResult()} 96 * 97 * @return the computed result of a leaf node 98 */ 99 DoLeaf(ctx context.Context) Sink 100 101 /** 102 * Retrieves a result previously stored with {@link #setLocalResult} 103 * 104 * @return local result for this node previously stored with 105 * {@link #setLocalResult} 106 */ 107 GetLocalResult() Sink 108 109 /** 110 * Associates the result with the task, can be retrieved with 111 * {@link #GetLocalResult} 112 * 113 * @param localResult local result for this node 114 */ 115 SetLocalResult(localResult Sink) 116 117 /** 118 * Decides whether or not to split a task further or compute it 119 * directly. If computing directly, calls {@code doLeaf} and pass 120 * the result to {@code setRawResult}. Otherwise splits off 121 * subtasks, forking one and continuing as the other. 122 * 123 * <p> The method is structured to conserve resources across a 124 * range of uses. The loop continues with one of the child tasks 125 * when split, to avoid deep recursion. To cope with spliterators 126 * that may be systematically biased toward left-heavy or 127 * right-heavy splits, we alternate which child is forked versus 128 * continued in the loop. 129 */ 130 Compute(ctx context.Context) 131 132 /** 133 * {@inheritDoc} 134 * 135 * @implNote 136 * Clears spliterator and children fields. Overriders MUST call 137 * {@code super.onCompletion} as the last thing they do if they want these 138 * cleared. 139 */ 140 OnCompletion(caller Task) 141 142 /** 143 * Returns whether this node is a "leftmost" node -- whether the path from 144 * the root to this node involves only traversing leftmost child links. For 145 * a leaf node, this means it is the first leaf node in the encounter order. 146 * 147 * @return {@code true} if this node is a "leftmost" node 148 */ 149 IsLeftmostNode() bool 150 151 /** 152 * Arranges to asynchronously execute this task in the pool the 153 * current task is running in, if applicable, or using the {@link 154 * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}. While 155 * it is not necessarily enforced, it is a usage error to fork a 156 * task more than once unless it has completed and been 157 * reinitialized. Subsequent modifications to the state of this 158 * task or any data it operates on are not necessarily 159 * consistently observable by any thread other than the one 160 * executing it unless preceded by a call to {@link #join} or 161 * related methods, or a call to {@link #isDone} returning {@code 162 * true}. 163 * 164 * @return {@code this}, to simplify usage 165 */ 166 Fork(ctx context.Context) 167 168 /** 169 * Returns the result of the computation when it 170 * {@linkplain #isDone is done}. 171 * This method differs from {@link #get()} in that abnormal 172 * completion results in {@code RuntimeException} or {@code Error}, 173 * not {@code ExecutionException}, and that interrupts of the 174 * calling thread do <em>not</em> cause the method to abruptly 175 * return by throwing {@code InterruptedException}. 176 * 177 * @return the computed result 178 */ 179 Join() Sink 180 181 /** 182 * Commences performing this task, awaits its completion if 183 * necessary, and returns its result, or throws an (unchecked) 184 * {@code RuntimeException} or {@code Error} if the underlying 185 * computation did so. 186 * 187 * @return the computed result 188 */ 189 Invoke(ctx context.Context) Sink 190 } 191 192 type TODOTask struct { 193 class.Class 194 195 parent Task 196 197 /** 198 * The spliterator for the portion of the input associated with the subtree 199 * rooted at this task 200 */ 201 spliterator spliterator.Spliterator 202 203 /** Target leaf size, common to all tasks in a computation */ 204 targetSize int // may be lazily initialized 205 /** 206 * The left child. 207 * null if no children 208 * if non-null rightChild is non-null 209 */ 210 leftChild Task 211 212 /** 213 * The right child. 214 * null if no children 215 * if non-null leftChild is non-null 216 */ 217 rightChild Task 218 219 /** The result of this node, if completed */ 220 localResult Sink 221 222 computer func(ctx context.Context) 223 224 wg sync.WaitGroup 225 } 226 227 /** 228 * Constructor for root nodes. 229 * 230 * @param helper The {@code PipelineHelper} describing the stream pipeline 231 * up to this operation 232 * @param spliterator The {@code Spliterator} describing the source for this 233 * pipeline 234 */ 235 func (task *TODOTask) WithSpliterator(spliterator spliterator.Spliterator) *TODOTask { 236 task.spliterator = spliterator 237 return task 238 } 239 240 /** 241 * Constructor for non-root nodes. 242 * 243 * @param parent this node's parent task 244 * @param spliterator {@code Spliterator} describing the subtree rooted at 245 * this node, obtained by splitting the parent {@code Spliterator} 246 */ 247 func (task *TODOTask) WithParent(parent Task, spliterator spliterator.Spliterator) *TODOTask { 248 task.parent = parent 249 task.spliterator = spliterator 250 task.targetSize = parent.TargetSize() 251 return task 252 } 253 254 func (task *TODOTask) GetSpliterator() spliterator.Spliterator { 255 return task.spliterator 256 } 257 258 func (task *TODOTask) GetLeafTarget() int { 259 return runtime.GOMAXPROCS(-1) << 2 260 } 261 262 func (task *TODOTask) LeftChild() Task { 263 return task.leftChild 264 } 265 266 func (task *TODOTask) SetLeftChild(task_ Task) { 267 task.leftChild = task_ 268 } 269 270 func (task *TODOTask) RightChild() Task { 271 return task.rightChild 272 } 273 274 func (task *TODOTask) SetRightChild(task_ Task) { 275 task.rightChild = task_ 276 } 277 278 func (task *TODOTask) TargetSize() int { 279 return task.targetSize 280 } 281 282 func (task *TODOTask) MakeChild(spliterator spliterator.Spliterator) Task { 283 panic(exception.NewUnsupportedOperationException()) 284 return nil 285 } 286 287 func (task *TODOTask) DoLeaf(ctx context.Context) Sink { 288 panic(exception.NewUnsupportedOperationException()) 289 return nil 290 } 291 292 /** 293 * Returns a suggested target leaf size based on the initial size estimate. 294 * 295 * @return suggested target leaf size 296 */ 297 func (task *TODOTask) suggestTargetSize(sizeEstimate int) int { 298 this := task.GetDerivedElse(task).(Task) 299 est := sizeEstimate / this.GetLeafTarget() 300 if est > 0 { 301 return est 302 } 303 return 1 304 } 305 306 /** 307 * Returns the targetSize, initializing it via the supplied 308 * size estimate if not already initialized. 309 */ 310 func (task *TODOTask) getTargetSize(sizeEstimate int) int { 311 if task.targetSize != 0 { 312 return task.targetSize 313 } 314 315 task.targetSize = task.suggestTargetSize(sizeEstimate) 316 return task.targetSize 317 } 318 319 /** 320 * Returns the local result, if any. Subclasses should use 321 * {@link #setLocalResult(Object)} and {@link #GetLocalResult()} to manage 322 * results. This returns the local result so that calls from within the 323 * fork-join framework will return the correct result. 324 * 325 * @return local result for this node previously stored with 326 * {@link #setLocalResult} 327 */ 328 func (task *TODOTask) getRawResult() Sink { 329 return task.localResult 330 } 331 332 /** 333 * Does nothing; instead, subclasses should use 334 * {@link #setLocalResult(Object)}} to manage results. 335 * 336 * @param result must be null, or an exception is thrown (this is a safety 337 * tripwire to detect when {@code setRawResult()} is being used 338 * instead of {@code setLocalResult()} 339 */ 340 func (task *TODOTask) SetRawResult(result Sink) { 341 if result != nil { 342 panic(exception.NewIllegalStateException()) 343 } 344 } 345 346 /** 347 * Retrieves a result previously stored with {@link #setLocalResult} 348 * 349 * @return local result for this node previously stored with 350 * {@link #setLocalResult} 351 */ 352 func (task *TODOTask) GetLocalResult() Sink { 353 return task.localResult 354 } 355 356 /** 357 * Associates the result with the task, can be retrieved with 358 * {@link #GetLocalResult} 359 * 360 * @param localResult local result for this node 361 */ 362 func (task *TODOTask) SetLocalResult(localResult Sink) { 363 task.localResult = localResult 364 } 365 366 func (task *TODOTask) IsLeaf() bool { 367 return task.leftChild == nil 368 } 369 370 func (task *TODOTask) IsRoot() bool { 371 this := task.GetDerivedElse(task).(Task) 372 return this.GetParent() == nil 373 } 374 375 func (task *TODOTask) GetParent() Task { 376 return task.parent 377 } 378 379 func (task *TODOTask) Compute(ctx context.Context) { 380 rs := task.spliterator 381 var ls spliterator.Spliterator 382 sizeEstimate := rs.EstimateSize() 383 sizeThreshold := task.getTargetSize(sizeEstimate) 384 385 var this = task.GetDerivedElse(task).(Task) 386 var forkRight bool 387 for { 388 select { 389 case <-ctx.Done(): 390 return 391 default: 392 } 393 394 if sizeEstimate <= sizeThreshold { 395 break 396 } 397 398 ls = rs.TrySplit() 399 if ls == nil { 400 break 401 } 402 403 var leftChild, rightChild, taskToFork Task 404 leftChild = this.MakeChild(ls) 405 rightChild = this.MakeChild(rs) 406 this.SetLeftChild(leftChild) 407 this.SetRightChild(rightChild) 408 409 if forkRight { 410 forkRight = false 411 rs = ls 412 this = leftChild 413 taskToFork = rightChild 414 } else { 415 forkRight = true 416 this = rightChild 417 taskToFork = leftChild 418 } 419 420 // fork 421 taskToFork.Fork(ctx) 422 423 sizeEstimate = rs.EstimateSize() 424 } 425 this.SetLocalResult(this.DoLeaf(ctx)) 426 } 427 428 func (task *TODOTask) Fork(ctx context.Context) { 429 go func() { 430 task.wg.Add(1) 431 defer task.wg.Done() 432 this := task.GetDerivedElse(task).(Task) 433 this.Compute(ctx) 434 this.OnCompletion(task.GetDerivedElse(task).(Task)) 435 }() 436 } 437 438 func (task *TODOTask) Join() Sink { 439 task.wg.Wait() 440 return task.getRawResult() 441 } 442 443 func (task *TODOTask) Invoke(ctx context.Context) Sink { 444 this := task.GetDerivedElse(task).(Task) 445 446 this.Fork(ctx) 447 return this.Join() 448 } 449 450 func (task *TODOTask) OnCompletion(caller Task) { 451 task.spliterator = nil 452 task.leftChild = nil 453 task.rightChild = nil 454 } 455 456 func (task *TODOTask) IsLeftmostNode() bool { 457 var node = task.GetDerivedElse(task).(Task) 458 for node != nil { 459 parent := node.GetParent() 460 if parent != nil && parent.LeftChild() != node { 461 return false 462 } 463 node = parent 464 } 465 return true 466 }