开发实时仿真软件时,一般留有实时输入数据接口,根据仿真步长接收实时数据。由于仿真软件还要进行业务处理,如果把业务处理流程直接放在实时输入接口内部,那么相当于把业务挂在了调用方的线程上,很容易把唯一的线程卡住,谈何实时仿真?所以,这里应该给业务运转单独起一个线程,输入接口只负责接收数据,业务线程专门负责内部计算,两个线程共同操作同一缓存,即线程同步。本文通过一个案例来分析线程同步的应用以及证明加锁的必要性。
首先,自定义一个线程基类。该类主要实现线程的开始、停止,以及用一个互斥锁保障线程安全。代码如下:
CThread.h
#pragma once
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
class CThread
{
public:
CThread();
virtual ~CThread();
void start();
void stop();
protected:
virtual void run() = 0;
bool m_running;
private:
thread m_thread;
mutex m_mutex;
};
注意,这里把run函数写成了纯虚函数,即必须在子类里面实现。
CThread.cpp
#include "CThread.h"
CThread::CThread()
{
m_running = false;
cout << "进入基类线程!进行构造 CThread\n";
}
CThread::~CThread()
{
cout << "进入基类线程! 进行析构~CThread!\n";
if (m_running)
{
stop();
}
}
void CThread::start()
{
lock_guard<mutex> lock(m_mutex);
if (!m_running)
{
m_thread = thread(&CThread::run, this);
m_running = true;
}
}
void CThread::stop()
{
lock_guard<mutex> lock(m_mutex);
if (m_running)
{
m_running = false;
m_thread.join();
}
}
接着,写一个业务类,即真正负责逻辑调度,各种业务相关计算的类。代码如下:
CProcess.h
#pragma once
#include "CThread.h"
#include <vector>
struct Struct_RTData
{
double x;
double y;
double z;
string id;
};
class CProcess : public CThread
{
public:
CProcess();
~CProcess();
void DataRTInput(vector<Struct_RTData> vecRtData); //实时输入接口);
protected:
void run();
private:
vector<Struct_RTData> mVecRtData; //实时数据缓存,共享变量,累加和删除同步进行
mutex mMutex; //锁
};
CProcess.cpp
#include "CProcess.h"
#include <Windows.h>
CProcess::CProcess()
{
start(); //构造时线程即开始
}
CProcess::~CProcess()
{
}
void CProcess::DataRTInput(vector<Struct_RTData> vecRtData)
{
lock_guard<mutex> lock(mMutex);
for (int i = 0; i < vecRtData.size(); i++)
{
mVecRtData.push_back(vecRtData[i]);
}
}
void CProcess::run()
{
while (m_running)
{
if (mVecRtData.size() > 0)
{
{
lock_guard<mutex> lock(mMutex);
mVecRtData.erase(mVecRtData.begin()); //删除共享数组首位的实时数据,表示这个数据已经计算完成
}
int time_ms = rand() % 1000; //耗时从0到1000毫秒随机变化
double t = GetTickCount64(); //系统相对时间
cout << "一帧数据业务处理完毕:" << t << "; size=" << mVecRtData.size() << " 耗时:" << time_ms << "ms\n";
this_thread::sleep_for(std::chrono::milliseconds(time_ms));
}
}
}
这里要特别注意的是输入接口和线程函数里面都通过lock_guard<mutex>进行加锁,以解决两个线程竞争操作同一内存的冲突!
最后,给出main函数:
#include <iostream>
#include "CProcess.h"
#include <thread>
#include <chrono>
#include <string>
using namespace std;
int main()
{
std::cout << "线程同步案例!\n";
CProcess* p = new CProcess();
this_thread::sleep_for(chrono::seconds(3));
for (size_t i = 0; i < 100000; i++)
{
vector<Struct_RTData> tVec;
for (size_t j = 0; j < 100; j++)
{
Struct_RTData tData;
int tmp = i * j;
tData.id = to_string(tmp);
tVec.push_back(tData);
}
p->DataRTInput(tVec); //模拟发送了一帧数据
this_thread::sleep_for(chrono::milliseconds(617)); //故意把仿真间隔设置成奇数
}
delete p;
}
为了证明这两处加锁的必要性,我们任意注释其中一处或者两处,运行一下代码,结果如下:
显然,这是访问内存冲突了。
然后,我们恢复两处加锁,再看结果:
两个线程相安无事!
总结:对于两个线程竞争同一内存的场景,在操作内存处进行加锁保护可以避免内存冲突。