1# Overview of Multithreaded Concurrency 2 3Concurrency models are programming paradigms designed to implement concurrent tasks in various scenarios. Common concurrency models include those based on shared memory and those based on message passing. 4 5The actor model, as a typical example of a message-passing concurrency model, eliminates the need for you to deal with the complex and sporadic issues associated with locks. It also offers relatively high concurrency, which has led to its widespread adoption and use. 6 7Currently, ArkTS provides two concurrency capabilities: TaskPool and Worker, both of which are implemented based on the actor model. 8 9For details about the comparison between the actor model and the shared memory concurrency model, see [Multithreaded Concurrency Models](#multithreaded-concurrency-models). 10 11## Multithreaded Concurrency Models 12 13Shared memory concurrency model: In this model, multiple threads execute tasks simultaneously. These threads rely on the same memory and have access permissions. Before accessing memory, threads must compete for and lock the memory's usage rights. Threads that fail to acquire the lock must wait for other threads to release the lock before proceeding. 14 15Actor model: In this model, each thread is an independent actor, which has its own memory. Actors trigger the behavior of each other through message transfer. They cannot directly access the memory space of each other. 16 17Different from the shared memory concurrency model, the actor model provides independent memory space for each thread. As such, it avoids memory preemption and resulting functional and performance issues. 18 19In the actor model, concurrent tasks and task results are transmitted through the inter-thread communication. 20 21This topic uses the classic producer-consumer problem as an example to illustrate the differences between these two models in solving specific problems. 22 23### Shared Memory Model 24 25The following figure illustrates how to solve the producer-consumer issue using the shared memory model. 26 27 28 29To prevent problems like dirty reads and writes caused by simultaneous access, only one producer or consumer can access a shared memory container at any given moment. This means that producers and consumers need to compete for the lock of the container. Once a role secures the lock, others must wait until the lock is released before they can attempt to access the container. 30 31```ts 32// The pseudocode is used here to help you understand the differences between the shared memory model and the actor model. 33class Queue { 34 // ... 35 push(value: number) {} 36 37 empty(): boolean { 38 // ... 39 return true 40 } 41 42 pop(value: number) :number { 43 // ... 44 return value; 45 } 46} 47 48class Mutex { 49 // ... 50 lock(): boolean { 51 // ... 52 return true; 53 } 54 55 unlock() { 56 57 } 58} 59class BufferQueue { 60 queue: Queue = new Queue() 61 mutex: Mutex = new Mutex() 62 add(value: number) { 63 // Attempt to acquire the lock. 64 if (this.mutex.lock()) { 65 this.queue.push(value) 66 this.mutex.unlock() 67 } 68 } 69 70 take(value: number): number { 71 let res: number = 0; 72 // Attempt to acquire the lock. 73 if (this.mutex.lock()) { 74 if (this.queue.empty()) { 75 res = 1; 76 } 77 let num: number = this.queue.pop(value) 78 this.mutex.unlock() 79 res = num; 80 } 81 return res; 82 } 83} 84 85// Construct a globally shared memory buffer. 86let g_bufferQueue = new BufferQueue() 87 88class Producer { 89 constructor() { 90 } 91 run() { 92 let value = Math.random() 93 // Access to the bufferQueue object across threads. 94 g_bufferQueue.add(value) 95 } 96} 97 98class ConsumerTest { 99 constructor() { 100 } 101 run() { 102 // Access to the bufferQueue object across threads. 103 let num = 123; 104 let res = g_bufferQueue.take(num) 105 if (res != null) { 106 // Add consumption logic here. 107 } 108 } 109} 110 111function Main(): void { 112 let consumer: ConsumerTest = new ConsumerTest() 113 let producer1: Producer = new Producer() 114 for (let i = 0;i < 0;i++) { 115 // Simulate the startup of multiple threads to execute a production task. 116 // let thread = new Thread() 117 // thread.run(producer.run()) 118 // consumer.run() 119 } 120} 121``` 122 123 124### Actor Model 125 126The following figure demonstrates how to use the TaskPool concurrency capability based on the actor model to solve the producer-consumer issue. 127 128 129 130In the actor model, different roles operate independently without sharing memory. Each role, such as the producer thread and the UI thread, runs within its own virtual machine instance, each with its own exclusive memory space. After generating a result, the producer sends the result to the UI thread through serialization. The UI thread processes the result and then sends a new task to the producer thread. 131 132```ts 133import { taskpool } from '@kit.ArkTS'; 134 135// Cross-thread concurrent tasks 136@Concurrent 137async function produce(): Promise<number> { 138 // Add production logic here. 139 console.info("producing..."); 140 return Math.random(); 141} 142 143class Consumer { 144 public consume(value: Object) { 145 // Add consumption logic here. 146 console.info("consuming value: " + value); 147 } 148} 149 150@Entry 151@Component 152struct Index { 153 @State message: string = 'Hello World' 154 155 build() { 156 Row() { 157 Column() { 158 Text(this.message) 159 .fontSize(50) 160 .fontWeight(FontWeight.Bold) 161 Button() { 162 Text("start") 163 }.onClick(() => { 164 let produceTask: taskpool.Task = new taskpool.Task(produce); 165 let consumer: Consumer = new Consumer(); 166 for (let index: number = 0; index < 10; index++) { 167 // Execute the asynchronous concurrent production task. 168 taskpool.execute(produceTask).then((res: Object) => { 169 consumer.consume(res); 170 }).catch((e: Error) => { 171 console.error(e.message); 172 }) 173 } 174 }) 175 .width('20%') 176 .height('20%') 177 } 178 .width('100%') 179 } 180 .height('100%') 181 } 182} 183``` 184 185You can also wait until all the producer's tasks are complete, and then pass the results to the UI thread through serialization. After the UI thread receives the results, the consumer can handle them all together. 186 187```ts 188import { taskpool } from '@kit.ArkTS'; 189 190// Cross-thread concurrent tasks 191@Concurrent 192async function produce(): Promise<number> { 193 // Add production logic here. 194 console.info("producing..."); 195 return Math.random(); 196} 197 198class Consumer { 199 public consume(value: Object) { 200 // Add consumption logic here. 201 console.info("consuming value: " + value); 202 } 203} 204 205@Entry 206@Component 207struct Index { 208 @State message: string = 'Hello World' 209 210 build() { 211 Row() { 212 Column() { 213 Text(this.message) 214 .fontSize(50) 215 .fontWeight(FontWeight.Bold) 216 Button() { 217 Text("start") 218 }.onClick(async () => { 219 let dataArray = new Array<number>(); 220 let produceTask: taskpool.Task = new taskpool.Task(produce); 221 let consumer: Consumer = new Consumer(); 222 for (let index: number = 0; index < 10; index++) { 223 // Execute the asynchronous concurrent production task. 224 let result = await taskpool.execute(produceTask) as number; 225 dataArray.push(result); 226 } 227 for (let index: number = 0; index < dataArray.length; index++) { 228 consumer.consume(dataArray[index]); 229 } 230 }) 231 .width('20%') 232 .height('20%') 233 } 234 .width('100%') 235 } 236 .height('100%') 237 } 238} 239``` 240 241## TaskPool and Worker 242 243ArkTS provides two concurrency capabilities for you to choose from: TaskPool and Worker. For details about their operation mechanisms and precautions, see [TaskPool](taskpool-introduction.md) and [Worker](worker-introduction.md). For details about their differences in the implementation features and use cases, see [Comparison Between TaskPool and Worker](taskpool-vs-worker.md). 244