问题:
现在有主线程和两个处理线程A、B,主线程一直在以毫秒为单位update数据,A线程每隔1秒将会受到主线程的发出的信号,进行一次update,B线程主要处理A线程中耗时的操作(即需要超过一秒时间的操作)。A线程的平均耗时低于一秒,实现可靠代码。
1.初步想法实现如下:
sys_thread.h
#ifndef THREAD_SYN_H
#define THREAD_SYN_H
#include <thread>
#include <chrono>
#include <iostream>
struct user_time
{
int _ms;
int _second;
};
class thread_syn
{
public:
void init()
{
_second = -1;
_event_update = true;
_event_data = true;
_update_thread = std::thread([this]()
{
this->update_thread();
});
_deal_data_thread = std::thread([this]()
{
this->deal_data_thread();
});
}
void update(const user_time &cur_time)
{
// todo每一毫秒做的事情
to_do_something_ms(cur_time);
// 到整秒通知update线程
if (_second < cur_time._second)
{
_event_update = false;
_second = cur_time._second;
}
}
void to_do_something_ms(const user_time &cur_time)
{
// 操作时间间隔小于1ms
}
void to_update_something(bool &deal_data)
{
deal_data = true;
}
void deal_data()
{
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
}
void update_thread()
{
while (1)
{
while (_event_update);
static int cout = 0;
std::cout << "update_thread()" << cout++<<std::endl;
// to do something
bool deal_data = false;
to_update_something(deal_data);
if (deal_data)
{
_event_data = false;
}
_event_update = true;
}
}
void deal_data_thread()
{
while (1)
{
while (_event_data);
static int cout = 0;
std::cout << "deal_data_thread()" << ++cout <<std::endl;
deal_data();
_event_data = true;
}
}
bool _event_update;
bool _event_data;
int _second;
std::thread _update_thread; // 处理updata线程
std::thread _deal_data_thread; // 耗时操作线程
};
#endif
main.cpp
#include "thread_syn.h"
int main()
{
thread_syn syn;
syn.init();
user_time time;
for (int j = 0; j < 100; ++j)
for (int i = 0; i < 1000; ++i)
{
time._second = j;
time._ms = i;
syn.update(time);
std::chrono::milliseconds sleep_time(1);
std::this_thread::sleep_for(sleep_time);
}
return 0;
}
运行结果如下:
会发现update线程虽然每次都通知了deal线程去处理事件,但是由于处理速度问题,deal线程丢失了部分处理次数,导致处理异常的出现,不符合设计的要求
2.初步解决办法如下:(初步解决2019.7.13)
初步想法是既然是丢失处理次数,那么把丢失的数据缓存起来就行了, sys_thread.h修改如下:
#ifndef THREAD_SYN_H
#define THREAD_SYN_H
#include <thread>
#include <chrono>
#include <iostream>
#include <queue>
#include <mutex>
struct user_time
{
int _ms;
int _second;
};
class thread_syn
{
public:
~thread_syn()
{
_deal_data_thread.join();
_update_thread.join();
}
void init()
{
_second = -1;
_update_thread = std::thread([this]()
{
this->update_thread();
});
_deal_data_thread = std::thread([this]()
{
this->deal_data_thread();
});
}
void update(const user_time &cur_time)
{
// todo每一毫秒做的事情
to_do_something_ms(cur_time);
// 到整秒通知update线程
if (_second < cur_time._second)
{
//_event_update = false;
std::lock_guard<std::mutex> lk(_que_lock);
_que_cache.push(cur_time._second);
_data_cond.notify_one();
_second = cur_time._second;
}
}
void to_do_something_ms(const user_time &cur_time)
{
// 操作时间间隔小于1ms
}
void to_update_something(bool &deal_data)
{
static int i = 0;
deal_data = true;
++i;
}
void deal_data()
{
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura);
}
void update_thread()
{
while (1)
{
std::unique_lock<std::mutex> lk(_que_lock);
// std::unique_lock<std::mutex> l2 = lk;
_data_cond.wait(lk, [this]
{
return !_que_cache.empty();
});
int second = _que_cache.front();
_que_cache.pop();
lk.unlock();
if (second == -1)
{
break;
}
static int cout = 0;
std::cout << "update_thread()次数" << ++cout <<
"update_thread()秒数" << second << std::endl;
// to do something
bool deal_data = false;
to_update_something(deal_data);
if (deal_data)
{
std::lock_guard<std::mutex> lk1(_que_lock1);
_que_cache1.push(second);
_data_cond1.notify_one();
}
}
}
void deal_data_thread()
{
while (1)
{
std::unique_lock<std::mutex> lk1(_que_lock1);
_data_cond1.wait(lk1, [this]() {return !_que_cache1.empty(); });
int second = _que_cache1.front();
_que_cache1.pop();
lk1.unlock();
if (second == -1)
{
break;
}
static int cout = 0;
std::cout << "deal_data_thread()次数" << ++cout <<
"秒数 "<<second <<std::endl;
deal_data();
}
}
int _second;
std::thread _update_thread; // 处理updata线程
std::thread _deal_data_thread; // 耗时操作线程
std::queue<int> _que_cache; // 缓存队列
std::mutex _que_lock; // 队列锁
std::queue<int> _que_cache1; // 缓存队列
std::mutex _que_lock1; // 队列锁
std::condition_variable _data_cond;
std::condition_variable _data_cond1;
};
#endif
通过两个缓存队列_que_cache和_que_cache1,将需要传递的数据缓存起来,同时通过condition_variable的wait函数去等待数据,wait函数有两个参数,第一个参数是unique_lock对象第二个参数是回调函数,返回值为bool,当有线程调用了notify_one或者notify_all唤醒当前线程,就会把unique_lock对象lock且进入wait中,并执行回调函数进行判断,如果返回为true,则继续下面的代码执行(同时需要在完成后续操作后手动释放lock),如果为false,则释放lock(),等待下一次调用notify_one或者notify_all。
执行结果如下:
总算数据没有丢失了,不过问题还没有解决
3、还存在的问题(已解决)
现在存在的问题时,我们发现执行完了后线程没有退出,只是通过join函数等待线程。那么如何通过科学的方法,把线程安全退出呢,同时保证数据执行完整呢?
析构函数完成如下:
向队列中增加一个标志位,退出线程循环,从而安全退出线程。
首先退出update线程,再退出deal线程
~thread_syn()
{
{
std::lock_guard<std::mutex> lk(_que_lock);
_que_cache.push(-1);
_data_cond.notify_one();
}
_update_thread.join();
{
std::lock_guard<std::mutex> lk1(_que_lock1);
_que_cache1.push(-1);
_data_cond1.notify_one();
}
_deal_data_thread.join();
}
增加如上代码后,就能在析构thread_syn对象时安全退出,运行结果如下:
还有就是,如果在析构对象时想立刻退出怎么办?不想等处理完剩余数据,直接退出的方法,这就留给自己去思考了。
4、待优化
现在完成了一个线程间的同步机制,不过还存在优化的空间,queue.push和queue.pop需要上锁,而且会造成多次拷贝占用栈的空间(分配空间是较慢的,而且容易爆栈)。
(1)可以采取不回收内存的方法,将queue中的数据保持在缓存池中,需要内存时从缓存池取,接收完成后归还给缓存池
(2)采取循环队列,可以不用锁住queue,不过需要注意队列存满时,数据丢失的问题(这时需要暂停前端线程,等待后台线程处理完成才能继续)。