• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![image_0000002001497485](figures/image_0000002001497485.png)
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![image_0000001964697544](figures/image_0000001964697544.png)
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