C++ - 多线程 - 子线程调用的函数用引用传参

本文探讨了在C++中子线程如何安全地访问主线程变量,通过实例讲解了const与ref的区别,以及它们在处理线程间通信中的角色。重点介绍了在函数参数传递中使用ref的必要性和const的隐性保护。

目录

1.不安全情况

2.安全的处理

3.const和ref之间有什么秘密联系?

4.网课链接


1.不安全情况

子线程用引用调用主线程中的变量是不安全的,尤其是在detach()的情况下,比如下面的例子:

class A {
public:
	int _m = 5;
	//可以把整形隐式转换成一个类对象
	A(int a) :_m(a) { cout << "调用构造函数   " <<  "类id: " << this_thread::get_id() << endl; }
	A(const A &a) :_m(a._m){ cout << "调用拷贝构造函数" << "拷贝类id: " << this_thread::get_id() << endl; }
	~A(){ cout << "调用析构函数" << endl; }
};
void myprint(const int &i ,const A &buf) {
	cout << "函数id: " << this_thread::get_id() << endl;
}

int main() {
	cout << "主id: " << this_thread::get_id() << endl;
	int mvar = 1;
	int mysecond = 12;
	thread my(myprint, mvar , mysecond);
	my.detach();

	return 0;
}

输出:

主id: 13788
调用构造函数

这种情况就是因为主线程已经结束了,回收了自己的变量后,子线程调用函数,并引用主线程的变量,当然是不行的,此时主线程变量已经没了。

2.安全的处理

就是在线程调用函数的时候,让主线程创建一个临时变量作为参数传递进去,这样就安全了,创建的方法就是在变量前加变量类型,如下:

thread my(myprint, int(mvar) , A(mysecond));

输出:

主id: 2920
调用构造函数   类id: 2920
调用拷贝构造函数拷贝类id: 2920
函数id: 调用析构函数析构类id: 2920

 可以看到该调用过程经历:主线程创建了一个临时变量->传递到函数后又由主线程将该临时变量拷贝了一份(因为这样情况下引用其实就变成值传递了)->子线程执行后续部分。

但此时主线程执行完了后,回收了变量,子线程中的拷贝变量怎么办呢?毕竟这些拷贝变量也是主线程拷贝的,会不会出问题呢?答案是不会,我们把detach改为join查看一下完整的打印结果看一下:

主id: 9116
调用构造函数   类id: 9116
调用拷贝构造函数拷贝类id: 9116
函数id: 12672
调用析构函数析构类id: 9116
调用析构函数析构类id: 12672

可以发现第一个析构其实是析构了主线程中的类,第二个析构是析构的拷贝类,其线程id是子线程的,所以说主线程虽然帮忙调用拷贝构造函数,这个对象的析构还是由子线程自己析构的。

detach没有完全打印并不是后面没执行,而是因为命令窗口是主线程的,主线程结束就不打印了即使子线程还在执行。

最好还是老老实实用join()吧!

3.const和ref之间有什么秘密联系?

我在听网课学习多线程时,偶然间发现了自己和老师的电脑跑的结果不一样,因此就引发了一个问题,这里整理一下,先看代码:

class A {
public:
	int _m = 5;
	A() { cout << "调用构造函数   " << "类id: " << this_thread::get_id() << endl; }
	A(const A &a) :_m(a._m){ cout << "调用拷贝构造函数" << "拷贝类id: " << this_thread::get_id() << endl; }
	~A(){ cout << "调用析构函数" << endl; }
};
void myprint(int &i ,A &buf) {
	cout << i++ << endl;    
	cout << buf._m++ << endl;      
	cout << "函数id: " << this_thread::get_id() << endl;
}

int main() {
	cout << "主id: " << this_thread::get_id() << endl;
	int mvar = 1;
	int mysecond = 12;
	A a;
    //thread my(myprint, ref(mvar) , ref(a)); 
	thread my(myprint, mvar , a);    //这样会报错,前面必须像上一行那样加ref()   
	cout << mvar << endl;     
	cout << a._m << endl;     
	my.join();

	return 0;
}

上述代码中,函数定义中参数是引用的形式,我在线程调用时前面必须加ref()才不报错,否则就会出现:

加了后输出:

主id: 2988
调用构造函数   类id: 2988
1
5
函数id: 15736
2
6
调用析构函数

但如果我在函数定义时给前面加上const,调用的时候就不用加ref()了!(加const同时也要把函数里修改变量的操作删掉)代码如下:

class A {
public:
	int _m = 5;
	A() { cout << "调用构造函数   " << "类id: " << this_thread::get_id() << endl; }
	A(const A &a) :_m(a._m){ cout << "调用拷贝构造函数" << "拷贝类id: " << this_thread::get_id() << endl; }
	~A(){ cout << "调用析构函数" << endl; }
};
void myprint(const int &i ,const A &buf) {
	/*cout << i++ << endl;
	cout << buf._m++ << endl;*/
	cout << "函数id: " << this_thread::get_id() << endl;
}

int main() {
	cout << "主id: " << this_thread::get_id() << endl;
	int mvar = 1;
	int mysecond = 12;
	A a;
	thread my(myprint, mvar , a);
	cout << mvar << endl;
	cout << a._m << endl;
	my.join();

	return 0;
}

此时输出:

主id: 13324
调用构造函数   类id: 13324
调用拷贝构造函数拷贝类id: 13324
函数id: 4504
1
5调用析构函数

调用析构函数

当然,此时加上也没错,不过输出结果变为:

主id: 14012
调用构造函数   类id: 14012
函数id: 88481

5
调用析构函数

也就是说加了ref就是正儿八经的引用,不加的话其实是用类似值传递的方法来形成了假的引用情况,但我们知道引用的话变量和他的引用的值应该是统一的,值传递形成的假引用为了满足这一条件,就用const限制用户:“如果你想用假引用,你就必须用const修饰别修改这个变量的值,否则值不统一用户就知道我是假引用啦!”

所以,如果想修改值,就老老实实用ref()引用或者变量前加mutable

ref(a) == &a

4.网课链接

c++11并发与多线程视频课程_哔哩哔哩_bilibili   的 P5

<think>好的,我现在需要帮助用户解决在Simulink Stateflow中结合状态机和微分方程的问题。首先,我得确认用户的具体需求。用户提到的是如何在Stateflow中使用状态机解决微分方程问题,所以需要结合状态机的逻辑控制和微分方程的数值求解。 首先,我应该回顾Stateflow的基本功能。Stateflow主要用于建模状态机和流程图,适合处理离散的状态转换和事件驱动的逻辑。而微分方程通常是连续系统的问题,需要用Simulink中的连续模块,比如积分器、微分方程求解器等。因此,结合这两者可能需要混合使用Stateflow的状态机和Simulink的连续模块。 接下来,我需要考虑用户可能的场景。例如,用户可能在设计个控制系统,其中某些模式切换需要状态机来控制,而每个模式下的动态行为由微分方程描述。比如,电机控制中的不同工作模式(恒转矩、恒功率等),每个模式下有不同的微分方程需要求解。 接下来,我应该思考如何在Stateflow中调用或集成微分方程求解。Stateflow本身主要是处理离散事件,但可以通过调用Simulink函数或者使用Matlab Function块来嵌入微分方程。此外,可能需要使用Simulink的连续模块来处理微分方程的积分部分,而Stateflow负责状态之间的转换条件。 然后,我需要考虑具体的实现步骤。例如,在Stateflow中定义状态,每个状态对应不同的微分方程模型。当状态切换时,对应的模型参数或方程也会改变。可能需要使用Simulink的触发子系统或使能子系统,由Stateflow的状态转换来激活不同的子系统,每个子系统包含对应的微分方程求解部分。 另外,要注意Stateflow中的动作部分,比如在进入某个状态时初始化变量,或者在状态持续期间更新方程的参数。可能需要使用状态中的entry、during、exit动作来设置变量,或者调用外部Simulink函数。 还需要考虑数值求解的连续性问题。Stateflow的状态机通常在离散时间步长内执行,而微分方程可能需要更精细的连续求解。因此,可能需要配置Simulink的求解器设置,确保在状态切换时,连续部分的求解也能正确进行,不会出现数值不稳定的情况。 然后,我应该想到用户可能需要个示例来说明这个过程。例如,个温度控制系统,状态机在加热、保温和关闭状态之间切换,每个状态下的温度变化由不同的微分方程描述。在Stateflow中定义这三个状态,状态转换由温度阈值触发,而每个状态对应的微分方程则在Simulink的连续子系统中实现。 最后,我需要验证这些思路是否正确。可能需要查阅SimulinkStateflow的文档,确认状态机如何与连续模块交互,以及如何在状态动作中调用积分器等组件。同时,检查是否有相关的示例或教程,确保建议的方法符合最佳实践。 总结下,解决方案应包含以下几个步骤:在Stateflow中设计状态机,每个状态对应不同的微分方程模型;使用Simulink的连续模块处理方程求解;通过触发子系统或函数调用实现状态切换时的模型切换;配置求解器参数以确保数值稳定性。同时,提供具体的实现示例,帮助用户理解如何操作。</think>### 在Simulink Stateflow中结合状态机与微分方程的步骤 #### 1. 理解StateflowSimulink的分工 - **Stateflow**:负责离散逻辑控制(如模式切换、条件触发)[^1]。 - **Simulink**:通过连续模块(如积分器、微分方程模块)实现动态系统建模[^2]。 #### 2. 实现微分方程的核心方法 在Stateflow中直接求解微分方程需借助以下两种方式: - **调用Simulink函数**:在状态动作中调用外部定义的Simulink函数(如积分器)。 - **联合触发子系统**:通过状态切换触发包含微分方程的子系统的执行。 #### 3. 具体操作步骤 **示例场景**:电机控制系统(恒转矩/恒功率模式切换) ```matlab % Stateflow中的模式切换逻辑(引用自示例代码[^3]) if load_torque > max_torque mode = 'constant_torque'; elseif load_power > max_power mode = 'constant_power'; else mode = 'max_efficiency'; end ``` **步骤1:设计Stateflow状态机** - 定义状态:`恒转矩模式`、`恒功率模式`、`最大效率模式`。 - 添加转移条件:基于传感器输入(如负载扭矩、功率)触发状态切换。 **步骤2:关联微分方程模型** -Simulink中为每个状态创建**使能子系统**: - 恒转矩模式:微分方程 $\frac{d\omega}{dt} = \frac{T_{load} - B\omega}{J}$。 - 恒功率模式:方程 $P = T\omega = \text{const}$,推导 $\frac{d\omega}{dt} = \frac{P/\omega - B\omega}{J}$。 - 使用`Integrator`模块对微分项进行数值求解。 **步骤3:状态动作绑定** -Stateflow状态的`entry`或`during`动作中: - 调用Simulink函数更新参数(如 $T_{load}$)。 - 触发对应子系统的使能信号。 #### 4. 关键配置与调试 - **求解器设置**:选择固定步长或变步长求解器(如`ode45`),需与Stateflow事件周期匹配。 - **数据传递**:通过`Data Store Memory`或`Global Variables`实现跨模块数据共享。 - **事件触发**:使用`Temporal Logic`(如`after`)或外部信号触发状态转移。 --- ### 完整示例模型结构 ``` Simulink模型层级: ├── Stateflow Chart(状态机控制) │ ├── State: 恒转矩模式 → 使能子系统1 │ ├── State: 恒功率模式 → 使能子系统2 │ └── 转移条件(基于传感器输入) ├── 使能子系统1(恒转矩微分方程) │ └── 包含Integrator、Gain等模块 ├── 使能子系统2(恒功率微分方程) └── 公共输出模块(如Scope、To Workspace) ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值