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