1# Overview of Multithreaded Concurrency 2 3Multithreaded concurrency refers to the execution of multiple threads simultaneously within a single program, enhancing performance and resource utilization through parallel or alternating task execution. In the development of ArkTS applications, there are many service scenarios that require multithreaded concurrency. These scenarios can be categorized into three main types. For more details, [Multithreaded Development Practice Cases](batch-database-operations-guide.md). 4 5- Service logic that involves heavy computation or multiple I/O read/write operations, which require extended execution time, such as image/video encoding and decoding, compression/decompression, and database operations. 6 7- Service logic that includes listening for or periodically collecting data, which requires continuous operation over extended periods, such as periodically collecting sensor data. 8 9- Service logic that follows the main thread's lifecycle or is bound to the main thread, such as in gaming platforms. 10 11 12Concurrency models are used to implement concurrent tasks in different usage scenarios. Common concurrency models include those based on shared memory and those based on message communication. 13 14The actor model is a typical example of a concurrency model based on message communication. It allows developers to avoid the complexity of dealing with locks and offers high concurrency, making it widely used. 15 16Currently, ArkTS provides two concurrency capabilities: TaskPool and Worker, both of which are implemented based on the actor model. 17 18For details about the comparison between the actor model and the shared memory concurrency model, see [Multithreaded Concurrency Models](#multithreaded-concurrency-models). 19 20## Multithreaded Concurrency Models 21 22Shared memory concurrency model: In this model, multiple threads execute tasks simultaneously. These threads rely on the same memory resource 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. 23 24Actor 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. 25 26Different from the shared memory concurrency model, the actor model provides independent memory space for each thread. As such, it avoids memory preemption and enhances development efficiency. 27 28In the actor model, tasks and task results are transmitted through the inter-thread communication. 29 30This topic uses the classic producer-consumer problem as an example to illustrate the differences between these two models in solving specific problems. 31 32### Shared Memory Model 33 34The following figure illustrates how to solve the producer-consumer issue using the shared memory model. 35 36 37 38To 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. 39 40```ts 41// The pseudocode is used here to help you understand the differences between the shared memory model and the actor model. 42class Queue { 43 // ... 44 push(value: number) { 45 } 46 47 empty(): boolean { 48 // ... 49 return true; 50 } 51 52 pop(value: number): number { 53 // ... 54 return value; 55 } 56} 57 58class Mutex { 59 // ... 60 lock(): boolean { 61 // ... 62 return true; 63 } 64 65 unlock() { 66 } 67} 68 69class BufferQueue { 70 queue: Queue = new Queue(); 71 mutex: Mutex = new Mutex(); 72 73 add(value: number) { 74 // Attempt to acquire the lock. 75 if (this.mutex.lock()) { 76 this.queue.push(value); 77 this.mutex.unlock(); 78 } 79 } 80 81 take(value: number): number { 82 let res: number = 0; 83 // Attempt to acquire the lock. 84 if (this.mutex.lock()) { 85 if (this.queue.empty()) { 86 res = 1; 87 return res; 88 } 89 let num: number = this.queue.pop(value); 90 this.mutex.unlock(); 91 res = num; 92 } 93 return res; 94 } 95} 96 97// Construct a globally shared memory buffer. 98let g_bufferQueue = new BufferQueue(); 99 100class Producer { 101 constructor() { 102 } 103 104 run() { 105 let value = Math.random(); 106 // Access to the bufferQueue object across threads. 107 g_bufferQueue.add(value); 108 } 109} 110 111class ConsumerTest { 112 constructor() { 113 } 114 115 run() { 116 // Access to the bufferQueue object across threads. 117 let num = 123; 118 let res = g_bufferQueue.take(num); 119 if (res != null) { 120 // Add consumption logic here. 121 } 122 } 123} 124 125function Main(): void { 126 let consumer: ConsumerTest = new ConsumerTest(); 127 let producer: Producer = new Producer(); 128 let threadNum: number = 10; 129 for (let i = 0; i < threadNum; i++) { 130 // The following pseudocode simulates the startup of multiple threads to execute production tasks. 131 // let thread = new Thread(); 132 // thread.run(producer.run()); 133 // consumer.run(); 134 } 135} 136``` 137 138 139### Actor Model 140 141The following figure demonstrates how to use the TaskPool concurrency capability based on the actor model to solve the producer-consumer issue. 142 143 144 145In 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. 146 147```ts 148import { taskpool } from '@kit.ArkTS'; 149 150// Cross-thread concurrent tasks 151@Concurrent 152async function produce(): Promise<number> { 153 // Add production logic here. 154 console.info('producing...'); 155 return Math.random(); 156} 157 158class Consumer { 159 public consume(value: Object) { 160 // Add consumption logic here. 161 console.info('consuming value: ' + value); 162 } 163} 164 165@Entry 166@Component 167struct Index { 168 @State message: string = 'Hello World'; 169 170 build() { 171 Row() { 172 Column() { 173 Text(this.message) 174 .fontSize(50) 175 .fontWeight(FontWeight.Bold) 176 Button() { 177 Text('start') 178 }.onClick(() => { 179 let produceTask: taskpool.Task = new taskpool.Task(produce); 180 let consumer: Consumer = new Consumer(); 181 for (let index: number = 0; index < 10; index++) { 182 // Execute the asynchronous concurrent production task. 183 taskpool.execute(produceTask).then((res: Object) => { 184 consumer.consume(res); 185 }).catch((e: Error) => { 186 console.error(e.message); 187 }) 188 } 189 }) 190 .width('20%') 191 .height('20%') 192 } 193 .width('100%') 194 } 195 .height('100%') 196 } 197} 198``` 199<!-- @[actor_model](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/MultithreadedConcurrency/MultiThreadConcurrencyOverview/entry/src/main/ets/pages/Index.ets) --> 200 201You 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. 202 203```ts 204import { taskpool } from '@kit.ArkTS'; 205 206// Cross-thread concurrent tasks 207@Concurrent 208async function produce(): Promise<number> { 209 // Add production logic here. 210 console.info('producing...'); 211 return Math.random(); 212} 213 214class Consumer { 215 public consume(value: Object) { 216 // Add consumption logic here. 217 console.info('consuming value: ' + value); 218 } 219} 220 221@Entry 222@Component 223struct Index { 224 @State message: string = 'Hello World' 225 226 build() { 227 Row() { 228 Column() { 229 Text(this.message) 230 .fontSize(50) 231 .fontWeight(FontWeight.Bold) 232 Button() { 233 Text('start') 234 }.onClick(async () => { 235 let dataArray = new Array<number>(); 236 let produceTask: taskpool.Task = new taskpool.Task(produce); 237 let consumer: Consumer = new Consumer(); 238 for (let index: number = 0; index < 10; index++) { 239 // Execute the asynchronous concurrent production task. 240 let result = await taskpool.execute(produceTask) as number; 241 dataArray.push(result); 242 } 243 for (let index: number = 0; index < dataArray.length; index++) { 244 consumer.consume(dataArray[index]); 245 } 246 }) 247 .width('20%') 248 .height('20%') 249 } 250 .width('100%') 251 } 252 .height('100%') 253 } 254} 255``` 256 257## TaskPool and Worker 258 259ArkTS 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). 260