@@ -975,8 +975,6 @@ bitmap.pixels = new Uint8Array(buffer, start);
975
975
976
976
## SharedArrayBuffer
977
977
978
- ### 共享内存
979
-
980
978
JavaScript 是单线程的,Web worker 引入了多线程:主线程用来与用户互动,Worker 线程用来承担计算任务。每个线程的数据都是隔离的,通过` postMessage() ` 通信。下面是一个例子。
981
979
982
980
``` javascript
@@ -1075,12 +1073,29 @@ onmessage = function (ev) {
1075
1073
};
1076
1074
```
1077
1075
1078
- ### Atomics 对象
1076
+ ## Atomics 对象
1079
1077
1080
1078
多线程共享内存,最大的问题就是如何防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供` Atomics ` 对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有进程内同步。
1081
1079
1082
1080
什么叫“原子性操作”呢?现代编程语言中,一条普通的命令被编译器处理以后,会变成多条机器指令。如果是单线程运行,这是没有问题的;多线程环境并且共享内存时,就会出问题,因为这一组机器指令的运行期间,可能会插入其他线程的指令,从而导致运行结果出错。请看下面的例子。
1083
1081
1082
+ ``` javascript
1083
+ // 主线程
1084
+ ia[42 ] = 314159 ; // 原先的值 191
1085
+ ia[37 ] = 123456 ; // 原先的值 163
1086
+
1087
+ // Worker 线程
1088
+ console .log (ia[37 ]);
1089
+ console .log (ia[42 ]);
1090
+ // 可能的结果
1091
+ // 123456
1092
+ // 191
1093
+ ```
1094
+
1095
+ 上面代码中,主线程的原始顺序是先对42号位置赋值,再对37号位置赋值。但是,编译器和 CPU 为了优化,可能会该改变这两个操作的执行顺序(因为它们之间互不依赖),先对37号位置赋值,再对42号位置赋值。而执行到一半的时候,Worker 线程可能就会来读取数据,导致打印出` 123456 ` 和` 191 ` 。
1096
+
1097
+ 下面是另一个例子。
1098
+
1084
1099
``` javascript
1085
1100
// 主线程
1086
1101
var sab = new SharedArrayBuffer (Int32Array .BYTES_PER_ELEMENT * 100000 );
@@ -1095,7 +1110,7 @@ ia[112]++; // 错误
1095
1110
Atomics .add (ia, 112 , 1 ); // 正确
1096
1111
```
1097
1112
1098
- 上面代码中,Worker 线程直接改写共享内存` ia[112]++ ` 是不正确的。因为这行语句会被编译成多条机器指令,这些指令之间无法保证不会插入其他进程的指令。
1113
+ 上面代码中,Worker 线程直接改写共享内存` ia[112]++ ` 是不正确的。因为这行语句会被编译成多条机器指令,这些指令之间无法保证不会插入其他进程的指令。请设想如果两个线程同时 ` ia[112]++ ` ,很可能它们得到的结果都是不正确的。
1099
1114
1100
1115
` Atomics ` 对象就是为了解决这个问题而提出,它可以保证一个操作所对应的多条机器指令,一定是作为一个整体运行的,中间不会被打断。也就是说,它所涉及的操作都可以看作是原子性的单操作,这可以避免线程竞争(),提高多线程共享内存时的操作安全。所以,` ia[112]++ ` 要改写成` Atomics.add(ia, 112, 1) ` 。
1101
1116
@@ -1116,15 +1131,16 @@ Atomics.store(array, index, value)
1116
1131
1117
1132
``` javascript
1118
1133
// 主线程 main.js
1119
- console . log ( ' notifying... ' );
1120
- Atomics .store (sharedArray, 0 , 123 );
1134
+ ia[ 42 ] = 314159 ; // 原先的值 191
1135
+ Atomics .store (ia, 37 , 123456 ); // 原先的值是 163
1121
1136
1122
1137
// Worker 线程 worker.js
1123
- while (Atomics .load (sharedArray, 0 ) !== 123 ) ;
1124
- console .log (' notified' );
1138
+ while (Atomics .load (ia, 37 ) == 163 );
1139
+ console .log (ia[37 ]); // 123456
1140
+ console .log (ia[42 ]); // 314159
1125
1141
```
1126
1142
1127
- 上面代码中,主线程的` Atomics.store ` 向 SharedBuffer 视图的0号位置,写入123。 Worker 线程的 ` Atomics.load ` 从该位置读出数据,只要不等于123就不断循环 。
1143
+ 上面代码中,主线程的` Atomics.store ` 向42号位置的赋值,一定是早于37位置的赋值。只要37号位置等于163, Worker 线程就不会终止循环,而对37号位置和42号位置的取值,一定是在 ` Atomics.load ` 操作之后 。
1128
1144
1129
1145
** (2)Atomics.wait(),Atomics.wake()**
1130
1146
@@ -1142,6 +1158,25 @@ Atomics.wake(sharedArray, index, count)
1142
1158
1143
1159
` Atomics.wake ` 用于唤醒` count ` 数目在` sharedArray[index] ` 位置休眠的线程,让它继续往下运行。
1144
1160
1161
+ 下面请看一个例子。
1162
+
1163
+ ``` javascript
1164
+ // 线程一
1165
+ console .log (ia[37 ]); // 163
1166
+ Atomics .store (ia, 37 , 123456 );
1167
+ Atomics .wake (ia, 37 , 1 );
1168
+
1169
+ // 线程二
1170
+ Atomics .wait (ia, 37 , 163 );
1171
+ console .log (ia[37 ]); // 123456
1172
+ ```
1173
+
1174
+ 上面代码中,共享内存视图` ia ` 的第37号位置,原来的值是` 163 ` 。进程二使用` Atomics.wait() ` 方法,指定只要` ia[37] ` 等于` 163 ` ,就进入休眠状态。进程一使用` Atomics.store() ` 方法,将` 123456 ` 放入` ia[37] ` ,然后使用` Atomics.wake() ` 方法将监视` ia[37] ` 的休眠线程唤醒。
1175
+
1176
+ 另外,基于` wait ` 和` wake ` 这两个方法的锁内存实现,可以看 Lars T Hansen 的 [ js-lock-and-condition] ( https://github.com/lars-t-hansen/js-lock-and-condition ) 这个库。
1177
+
1178
+ 注意,浏览器的主线程有权“拒绝”休眠,这是为了防止用户失去响应。
1179
+
1145
1180
** (3)运算方法**
1146
1181
1147
1182
共享内存上面的某些运算是不能被打断的,即不能在运算过程中,让其他线程改写内存上面的值。Atomics 对象提供了一些运算方法,防止数据被改写。
@@ -1186,20 +1221,3 @@ Atomics.xor(sharedArray, index, value)
1186
1221
1187
1222
` Atomics.compareExchange ` 的一个用途是,从 SharedArrayBuffer 读取一个值,然后对该值进行某个操作,操作结束以后,检查一下 SharedArrayBuffer 里面原来那个值是否发生变化(即被其他线程改写过)。如果没有改写过,就将它写回原来的位置,否则读取新的值,再重头进行一次操作。
1188
1223
1189
- ### Atomics 对象的例子
1190
-
1191
- ``` javascript
1192
- // 线程一
1193
- console .log (ia[37 ]); // 163
1194
- Atomics .store (ia, 37 , 123456 );
1195
- Atomics .wake (ia, 37 , 1 );
1196
-
1197
- // 线程二
1198
- Atomics .wait (ia, 37 , 163 );
1199
- console .log (ia[37 ]); // 123456
1200
- ```
1201
-
1202
- 上面代码中,共享内存视图` ia ` 的第37号位置,原来的值是` 163 ` 。进程二使用` Atomics.wait() ` 方法,指定只要` ia[37] ` 等于` 163 ` ,就进入休眠状态。进程一使用` Atomics.store() ` 方法,将` 123456 ` 放入` ia[37] ` ,然后使用` Atomics.wake() ` 方法将监视` ia[37] ` 的一个休眠线程唤醒。
1203
-
1204
- 另外,基于` wait ` 和` wake ` 这两个方法的锁内存实现,可以看 Lars T Hansen 的 [ js-lock-and-condition] ( https://github.com/lars-t-hansen/js-lock-and-condition ) 这个库。
1205
-
0 commit comments