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

       显然不是。

       

       ++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释放锁。这种情况下,假设计算机有多个核,本可以将两个线程放在不同核上并行执行的,这样加锁使两个线程串行执行,也快不到哪里去。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C语言中,i++操作符本身是不具备线程安全性的。当多个线程同时对同一个变量进行i++操作时,可能会出现竞态条件(Race Condition)的问题。 竞态条件是指多个线程对共享资源的访问产生冲突,导致程序执行结果与期望不符。在i++操作中,实际上包含了读取变量i的值、对其进行加1操作、将结果写回变量i这三个步骤。如果多个线程同时执行这些步骤,就可能导致结果不确定或错误。 为了保证线程安全,可以采取以下措施之一: 1. 使用互斥锁(Mutex Lock):在每个线程访问i++操作之前,首先获取一个互斥锁,确保只有一个线程能够执行i++操作,其他线程需要等待锁的释放。这样可以避免竞态条件的发生。 2. 使用原子操作(Atomic Operation):一些编程语言和库提供了原子操作的支持,这些操作可以保证在执行期间不会被中断,从而避免了竞态条件。在C语言中,可以使用GCC内置的原子操作函数(如__sync_fetch_and_add)或者C11标准中的原子操作类型(如atomic_int)来实现线程安全的i++操作。 3. 使用线程局部存储(Thread-Local Storage):如果每个线程都有自己的i变量副本,那么就不会有竞态条件的问题。可以使用线程局部存储来实现每个线程都有独立的i变量。 需要注意的是,以上措施都需要根据具体情况和需求来选择和实现。在多线程环境下,对共享资源的访问需要谨慎处理,确保线程安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值