++i和i++是线程安全的吗

得意时要看淡,失意时要看开。不论得意失意,切莫大意;不论成功失败,切莫止步。志得意满时,需要的是淡然,给自己留一条退路;失意落魄时,需要的是泰然,给自己觅一条出路++i和i++是线程安全的吗,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

       显然不是。

       

       ++i和i++的区别在于一个是先自增再赋值,一个是先赋值再自增。(大家应该都知道,不详细举例子了)

       
       ++i和i++的过程可以分为3步, 这3步并不是一个原子操作, 一个线程在执行①、②、③步的过程中,是可能被其他线程打断的:

  • ①从内存读入临时i值,假设为temp,放在寄存器
  • ②在寄存器中对temp值+1
  • ③将寄存器中temp+1的值写回内存赋给i

       举个例子,假设现在i是一个全局变量,有2个线程要各自同时对i自增1000w次。代码如下:

#include <iostream>
#include <vector>
#include <thread>
using namespace std;
int i = 0;	  // 全局变量
void fun() {
	for (int j = 0; j < 10000000; ++j) {
		++i;
	}
}
int main() {
	vector<thread> vec;
	for (int j = 0; j < 2; ++j) {
		vec.emplace_back(fun);
		vec[j].detach();		// 2个线程同时执行fun()函数对i进行自增
	}	
	this_thread::sleep_for(chrono::seconds(5));	// 等待2个线程运行结束
	cout << i << endl;	
}

        以上代码就可能出现这样的问题:
        假设此时i值为100,线程1在进行了上面的①、②后,正准备进行③将101的值赋给i, 但是, 在线程1进行①、②步骤后,可能CPU分配给线程1的时间片用完了,现在CUP开始执行线程2,线程2对值为100的i进行自增,假设已经增加到了200, 然后, 线程2时间片用完,CPU接着执行线程1, 这个时候问题就来了, 线程1会将先前保存的temp+1值,也就是101赋给i,然后i本应该是201的,现在值却只有101。
        因此上面代码的输出结果,本应该是2* 1000w,但运行结果可能会比2* 1000w小,而且每次运行的结果可能不同,因为多个线程之间存在竞争关系。
在这里插入图片描述

       

       

       如何让以上程序达到预期效果,也就是一个线程在对全局变量进行操作的时候不被其他线程打断(或者说多个线程互斥地访问共享资源)呢?可以采用加锁的方法,对程序修改如下:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
int i = 0;
mutex m;	// 全局锁
void fun() {	
	for (int j = 0; j < 10000000; ++j) {
		m.lock();
		i++;		// 对i++操作进行加锁
		m.unlock();
	}	
}
int main(){
	vector<thread> vec;
	for (int j = 0; j < 2; ++j) {
		vec.emplace_back(fun);
		vec[j].detach();		
	}
	// 休眠30秒等待2个线程运行结束,不等它们运行结束(解锁)就去访问被上锁了的i值,会报错"mutex destroyed while busy"
	this_thread::sleep_for(chrono::seconds(30));	
	cout << i << endl;		
}

  
  

       但是,以上程序的运行时间会很长,因为每进行一个自增操作,都要加锁、解锁,上面的程序要加锁解锁2*1000w次!我们知道,加锁解锁是有时间开销的,这样频繁地加锁会消耗很长的时间。

       可以改变锁住的区域,将上面的程序改为:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
int i = 0;
mutex m;	// 全局锁
void fun() {
	m.lock();	
	for (int j = 0; j < 10000000; ++j) {		
		i++;		// 对i++操作进行加锁		
	}
	m.unlock();	
}
int main(){
	vector<thread> vec;
	for (int j = 0; j < 2; ++j) {
		vec.emplace_back(fun);
		vec[j].detach();		
	}
	// 休眠10秒等待2个线程运行结束,不等它们运行结束(解锁)就去访问被上锁了的i值,会报错"mutex destroyed while busy"
	this_thread::sleep_for(chrono::seconds(10));	
	cout << i << endl;		
}

       这样一共只需要进行2次加锁和解锁。但是,这样等同于让线程1和线程2串行执行,线程1在没有完成1000w次自增时不会解锁,那么线程2执行时无法获得该互斥锁,就会被阻塞起来,直到线程1释放锁。这种情况下,假设计算机有多个核,本可以将两个线程放在不同核上并行执行的,这样加锁使两个线程串行执行,也快不到哪里去。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/157139.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!