线程同步问题(更新2019.7.13)

       问题:

       现在有主线程和两个处理线程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,不过需要注意队列存满时,数据丢失的问题(这时需要暂停前端线程,等待后台线程处理完成才能继续)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值