j江苏省建设工程招投标网站,中山专业门户网站制作咨询,工作期间员工花钱做的网站,诚讯通网站什么是原子性
从一个例子说起#xff0c; x #xff0c;读和写 #xff0c; 如图假设多线程#xff0c;线程1和线程2同时操作变量x#xff0c;进行x的操作#xff0c;那么由于写的过程中#xff0c;都会先读一份x数据到cpu的寄存器中#xff0c;所以这个时候cpu1 和 c…什么是原子性
从一个例子说起 x 读和写 如图假设多线程线程1和线程2同时操作变量x进行x的操作那么由于写的过程中都会先读一份x数据到cpu的寄存器中所以这个时候cpu1 和 cpu2 拿到了相同的变量x假设初始x值为1则cpu1拿到的x为1cpu2拿到的x为1都操作并写回给x后x的值为2。
预期加两次结果为3但是实际由于多线程同时操作同一个变量了 可能产生写覆盖。进一步看这其中还要再提起一个词中断。
中断
多线程 - cpu中断
多线程下常见一个或者多个操作在 CPU 执行时候中断切出再切回。
对于多线程来说程序在运行一段代码的时候可能会中途切出这种来回切出和切回就出现了上面x的情况。产生了写覆盖的问题。
那么不用多线程只用单线程是不是就不会存在中断的问题是不是就安全了其实也不安全。因为线程下面还有协程如python Coroutine或如nodejs中 event loop其虽然不会在cpu运算的时候切出但是会在等待io的时候切出。 单线程 - io中断
单线程下一个或者多个IO操作执行的过程中中断切出再切回。
一个单线程切出的例子拿nodejs中event loop举例worker1 和 worker2分别产生event去累加result但是在累加的过程中会await sleep 模拟等待io这会导致由于等待io而引起的中断切出。
非原子性示例
function sleep(ms: number) {return new Promise(resolve setTimeout(resolve, ms));
}let result 0;async function worker1() {let maxtime1 1;while(maxtime1 100) {let name worker1;// 执行100次)console.log(${name} calculate current time ${maxtime1})// 开始工作let resultCopy result;// 让出await sleep(10);resultCopy 1;result resultCopy;maxtime1 1;}
}async function worker2() {let maxtime2 1;while(maxtime2 100) {let name worker2;// 执行100次console.log(${name} calculate current time ${maxtime2})// 开始工作let resultCopy result;// 让出await sleep(10);resultCopy 1;result resultCopy;maxtime2 1;}
}(async () {console.log(start calculate)const startTime Date.now();Promise.all([worker1(), worker2()]).then(() {const endTime Date.now();// 预期是200 但是由于会写覆盖所以最终小于200.console.log(耗时: ${endTime - startTime}ms);console.log(result:, result);}).catch((error) {console.error(A worker failed with error:, error);});
})()
运行结果通过结果 甚至输出结果直接就是100因为worker1 和 worker2的并行执行导致每次累加计算前worker1 和 worker2 都拿到相同的值 那么如何避免这种情况让worker1的代码片段执行完再执行的worker2的代码片段不切出达到原子性一种方法就是加锁下面继续看如何加锁达到原子性
原子性示例
通过加锁可以实现代码片段的原子性 如下
import { Mutex } from async-mutex;
const mutex new Mutex();function sleep(ms: number) {return new Promise(resolve setTimeout(resolve, ms));
}let result 0;async function worker1() {let maxtime1 1;// 执行100次while(maxtime1 100) {let name worker1;// 开始工作// 锁住,const release await mutex.acquire();console.log(${name} calculate current time ${maxtime1}, before start calulate result: ${result})// rlet resultCopy result;// 让出cpu这里即使让出其它worker由于无法获取锁所以会一直等待await sleep(10);resultCopy 1;// w result resultCopy;console.log(${name} calculate current time ${maxtime1}, after calulate result: ${result})release();maxtime1 1;}
}async function worker2() {let maxtime2 1;// 执行100次while(maxtime2 100) {let name worker2;// 开始工作// 锁住,const release await mutex.acquire();console.log(${name} calculate current time ${maxtime2}, before start calulate result: ${result})// rlet resultCopy result;// 让出cpuawait sleep(10);resultCopy 1;// w result resultCopy;console.log(${name} calculate current time ${maxtime2}, after calulate result: ${result})release();maxtime2 1;}
}(async () {console.log(start calculate)const startTime Date.now();Promise.all([worker1(), worker2()]).then(() {const endTime Date.now();// 预期是200 但是由于会写覆盖所以最终小于200.console.log(耗时: ${endTime - startTime}ms);console.log(result:, result);}).catch((error) {console.error(A worker failed with error:, error);});
})()
此时在看输出结果可以发现由于有锁worker1 和 worker2是串行累加的不会在执行累加的过程中切出所以最终累加的结果是200符合预期。 同时可以发现由于加锁整体串行会导致整体运行时间增加。这里就不得不多提下Event Loop 是一种异步编程模型io切出本身属于提高效率的设计所以如果不是需要原子性不是同时操作同一个变量则没必要加锁降低效率。
结语
总结 对于编程中的原子性如果说一段代码是原子性的则这段代码无论是cpu 还是 io等待 都不能被切出。这段代码需要完整的执行这才是我们预期的一段代码的原子性。