简体   繁体   中英

thread is block the main() of the code c++

I'm trying to implement thread for a wait() method, in such a way that it should not block the main() thread.
In the main thread, wait() function has to run the validate function every hour. so, The main() thread is blocked while the wait() function is running and will not continue executing any code until the wait() function completes or the thread is joined. so i want to run wait() independently from the main().

main.cpp

#include <iostream>
#include <fstream>
#include <time.h>
#include <sys/stat.h>
#include <boost/date_time.hpp>
#include <boost/thread/thread.hpp> 
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <pthread.h>

using namespace std;

constexpr char clock_file[] = "/home/saman/Desktop/clock";
constexpr char sync_file[] = "/home/saman/Desktop/hi/hi/synchronized";

class TimeEvent {
public:
     void receive_time_event(timespec& time) {
        // Create clock file
        ofstream clockFile(clock_file);
        if (!clockFile.is_open()) {
            cout << "Failed to open file" << endl;
        }
        clockFile.close();
        // Create synchronized file
        boost::filesystem::path dir(sync_file);
        boost::filesystem::create_directories(dir.parent_path());
        ofstream synchronizedFile(sync_file);
        if (!synchronizedFile.is_open()) {
            cout << "Failed to open file" << endl;
        }
        synchronizedFile.close();
    }

    timespec get_latest_clock_entry(){
    cout << "hello" << endl;
}
    void wait(){
        while(1){
        cout << "start wait" << endl;
        auto Now_Time = boost::posix_time::second_clock::local_time();
        auto update_Time = Now_Time + boost::posix_time::minutes(1);
        auto counter = update_Time - Now_Time;
        boost::this_thread::sleep(counter);
        validate();
        }
    }

    void validate(){
        // cout << "hi" << endl;
        timespec currentTime;  
        timespec_get(&currentTime, TIME_UTC);
        timespec ModifiedTime = get_latest_clock_entry();
        if (currentTime.tv_sec > ModifiedTime.tv_sec){
            ofstream clockfile(clock_file);
            if (!clockfile.is_open()) {
            cout << "Failed to open file" << endl;
        }
        clockfile.close();
        }
        // update system time to match the clock file's modified time
        int result = clock_settime(CLOCK_REALTIME, &ModifiedTime);
        if(result == 0){
            cout<< "updated system time to current time" << endl;
        }
        else{
            cout<< "failed to update system time" << endl;
        }
    }
    void update_system_time_on_startup() {
           cout << "hello" << endl;
};

int main() {
    TimeEvent timeEvent;
    timespec time;
    timespec_get(&time, TIME_UTC);
    cout << "hello" << endl;
    timeEvent.receive_time_event(time);
    auto lastModifiedTime = timeEvent.get_latest_clock_entry();
    boost::thread Thread(&TimeEvent::wait, &timeEvent); 
    // boost::thread Thread1(boost::bind(&TimeEvent::wait, &timeEvent));
    cout << "good" << endl;
    timeEvent.update_system_time_on_startup();
    Thread.join();
    return 0;
}

so i want to run wait() independently from the main()

You do it by running the wait in a separate thread. Of course you already do that. And you can in fact already run other code in main , which you already do, although very little:

cout << "good" << endl;

This is printed while the wait is running on your thread. All is good. Of course, if you wanted to do more things, go for it. Once you join() the Thread your main will block until the Thread exits.

Let me make some observations I found during code review, then give you ideas on graceful shutdown of the background thread.

First Some Observations

One reason why the behavior of you program will not match your expectations is the Undefined Behavior in get_latest_clock_entry() :

timespec get_latest_clock_entry() {
    std::cout << "hello" << std::endl;
    return {}; // TODO, no return is Undefined Behavior!
}

Anything could happen, but certainly the dependent condition currentTime.tv_sec > ModifiedTime.tv_sec cannot be reliable.

Further, it may be unreliable to wait based on clock values in a system where the clock is adjusted on-the-fly, as your code does with ::clock_settime(CLOCK_REALTIME) . Certainly, as written, the code will behave erratically, as you literally use clock_settime with indeterminate values (as noted previously).

There is also a race-condition between the start of the thread and your update_system_time_on_startup . Currently, obviously it does nothing (but say "hello") but as soon as it does something more (like what it says), it will (a) touch shared state without synchronization (b) affect the wait timing code.

receive_time_event weirdly ignores the argument. Is that on purpose? Why is passed by lvalue-reference? Would it need to be updated?

Suggestions

You should probably explain better what you're actually trying to achieve.

Assuming you're somehow trying to synchronize different system clocks, looks like

if (currentTime.tv_sec > ModifiedTime.tv_sec)
    touch(clock_file);

aims to, once a minute:

  • update the clock_file if "our realtime clock" is >1s ahead of "some clock entry? from some other source?"
  • unconditionally set our clock equal to that "other source"

Already that doesn't match your problem description as in the question. Also, your wait code should

  1. use a monotonic clock to prevent bugs under changing clocks
  2. use a precalculated deadline instead of blindly adding 60s to a given time
  3. not really bother about UTC vs locatime

Let's review and simplify some of the other code:

static void touch(fs::path p) {
    create_directories(p.parent_path());
    fs::ofstream ofs(p);
    if (!ofs)
        throw std::runtime_error("Failed to create " + p.native());
}

static ::timespec getCurrentTime() {
    timespec currentTime;
    timespec_get(&currentTime, TIME_UTC);
    return currentTime;
}

void validate() {
    // std::cout << "hi" << std::endl;
    timespec currentTime  = getCurrentTime();
    timespec ModifiedTime = get_latest_clock_entry();

    if (currentTime.tv_sec > ModifiedTime.tv_sec)
        touch(clock_file);

    // update system time to match the clock file's modified time
    if (0 != clock_settime(CLOCK_REALTIME, &ModifiedTime))
        throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
}

To remove the race condition, just move update_system_time_on_startup inside the timeEvent class. Better yet, move the entire instance inside the thread!

void synchronizationThread() {
    try {
        TimeEvent timeEvent;
        timeEvent.update_system_time_on_startup();
        timeEvent.wait();
    } catch (std::exception const& e) {
        std::cerr << "synchronizationThread error: " << e.what() << std::endl;
    }
}

int main() {
    boost::thread sync(synchronizationThread);

    sync.join();
}

As mentioned, the wait code is overly complicated (cleaned up version):

void wait() {
    while (1) {
        std::cout << "start wait" << std::endl;
        ptime Now_Time    = boost::posix_time::second_clock::local_time(),
              update_Time = Now_Time + boost::posix_time::minutes(1);

        boost::posix_time::time_duration counter = update_Time - Now_Time;
        std::cerr << "Sleeping for " << counter << "\n";
        boost::this_thread::sleep(counter);
        boost::this_thread::sleep_for(60s);
        validate();
    }
}

It could be tons simpler, while being equivalent:

void wait() {
    while (1) {
        std::cout << "start wait" << std::endl;
        std::this_thread::sleep_for(60s);
        validate();
    }
}

Note that we now no longer need posix_time and need not link to the Boost Datetime library. In fact, we can replace boost::thread and boost::filesystem with std::thread and std::filesystem and be without any boost dependency .

Of course, this assumes that validate() takes no time, or it is okay for subsequent validates() to be more than 60s apart. Frankly, that sounds surprising in a situation where time synchronization sounds important, so I'd write what I mean , explicitly using a monotonous clock:

void wait() {
    using Clock   = std::chrono::steady_clock; // monotonous!

    auto next_cycle = [start = Clock::now()] {
        auto deadline = start;
        while (deadline <= Clock::now())
            deadline += cycle_interval;
        std::this_thread::sleep_until(deadline);
        return true;
    };

    while (next_cycle()) {
        validate();
    }
}

Obviously defining the interval suitable like:

constexpr auto cycle_interval = 60s;

Live Demo

Live On Coliru

#include <cassert>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <thread>

#include <sys/stat.h>
#include <time.h>

namespace fs = std::filesystem;
using namespace std::chrono_literals;

constexpr auto& clock_file     = "/home/saman/Desktop/clock";
constexpr auto& sync_file      = "/home/saman/Desktop/hi/hi/synchronized";
constexpr auto  cycle_interval = 5s; // 60s

class TimeEvent {
    static void touch(fs::path p) {
        create_directories(p.parent_path());
        std::ofstream ofs(p);
        if (!ofs)
            throw std::runtime_error("Failed to create " + p.native());
    }
    static ::timespec getCurrentTime() {
        timespec currentTime;
        timespec_get(&currentTime, TIME_UTC);
        return currentTime;
    }

  public:
    void receive_time_event(timespec /*time*/) {
        touch(clock_file);
        touch(sync_file);
    }

    timespec get_latest_clock_entry() { return getCurrentTime(); }

    void wait() {
        using Clock = std::chrono::steady_clock; // monotonous!

        auto next_cycle = [start = Clock::now()] {
            auto deadline = start;
            while (deadline <= Clock::now())
                deadline += cycle_interval;

            std::cout << "Waiting " << (deadline - Clock::now()) / 1ms << "ms" << std::endl;
            std::this_thread::sleep_until(deadline);
            return true;
        };

        while (next_cycle()) {
            validate();
        }
    }

    void validate() {
        std::cout << "validate()" << std::endl;
        timespec currentTime  = getCurrentTime();
        timespec ModifiedTime = get_latest_clock_entry();

        if (currentTime.tv_sec > ModifiedTime.tv_sec)
            touch(clock_file);

        {
            // I don't want to risk messing up my systemclock while doing the testing
            auto tie = [](timespec const& ts) { return std::tie(ts.tv_sec, ts.tv_nsec); };
            assert(tie(currentTime) <= tie(ModifiedTime));
        }

        // update system time to match the clock file's modified time
        if (0 != clock_settime(CLOCK_REALTIME, &ModifiedTime))
            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
    }

    void update_system_time_on_startup() {
        std::cout << "update_system_time_on_startup" << std::endl;
        receive_time_event(getCurrentTime());
    };
};

void synchronizationThread() {
    try {
        TimeEvent timeEvent;
        timeEvent.update_system_time_on_startup();
        timeEvent.wait();
    } catch (std::exception const& e) {
        std::cerr << "synchronizationThread error: " << e.what() << std::endl;
    }
}

int main() {
    std::jthread sync(synchronizationThread);

    for (int i = 0; i < 27; ++i) {
        std::this_thread::sleep_for(1s);
        std::cout << "." << std::flush;
    }
}

Locally:

在此处输入图像描述

BONUS: Graceful Shutdown

Of course you want graceful shutdown within reasonable time limits:

Live On Coliru

#include <atomic>
#include <cassert>
#include <condition_variable>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>

#include <sys/stat.h>
#include <time.h>

namespace fs = std::filesystem;
using namespace std::chrono_literals;

constexpr auto& clock_file     = "/home/saman/Desktop/clock";
constexpr auto& sync_file      = "/home/saman/Desktop/hi/hi/synchronized";
constexpr auto  cycle_interval = 5s; // 60s

class TimeEvent {
    static void touch(fs::path p) {
        create_directories(p.parent_path());
        std::ofstream ofs(p);
        if (!ofs)
            throw std::runtime_error("Failed to create " + p.native());
    }
    static ::timespec getCurrentTime() {
        timespec currentTime;
        timespec_get(&currentTime, TIME_UTC);
        return currentTime;
    }

    using Clock = std::chrono::steady_clock; // monotonous clock
    Clock::time_point const start = Clock::now();
    std::mutex              mx;
    std::condition_variable cv;
    std::atomic_bool        shutdown_requested{false};

    bool next_cycle() {
        auto deadline = start;
        while (deadline <= Clock::now())
            deadline += cycle_interval;

        std::cout << "Waiting " << (deadline - Clock::now()) / 1ms << "ms" << std::endl;

        std::unique_lock<std::mutex> lk(mx);
        cv.wait_until(lk, deadline, [this] { return shutdown_requested.load(); });
        return !shutdown_requested;
    };

  public:
    void shutdown() {
        shutdown_requested = true;
        std::lock_guard<std::mutex> lk(mx);
        cv.notify_all();
    }

    void receive_time_event(timespec /*time*/) {
        touch(clock_file);
        touch(sync_file);
    }

    timespec get_latest_clock_entry() { return getCurrentTime(); }

    void wait() {
        try {
            update_system_time_on_startup(); // optionally?

            while (next_cycle()) {
                validate();
            }
        } catch (std::exception const& e) {
            std::cerr << "synchronizationThread error: " << e.what() << std::endl;
        }
    }

    void validate() {
        std::cout << "validate()" << std::endl;
        timespec currentTime  = getCurrentTime();
        timespec ModifiedTime = get_latest_clock_entry();

        if (currentTime.tv_sec > ModifiedTime.tv_sec)
            touch(clock_file);

        {
            // I don't want to risk messing up my systemclock while doing the testing
            auto tie = [](timespec const& ts) { return std::tie(ts.tv_sec, ts.tv_nsec); };
            assert(tie(currentTime) <= tie(ModifiedTime));
        }

        // update system time to match the clock file's modified time
        if (0 != clock_settime(CLOCK_REALTIME, &ModifiedTime))
            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
    }

    void update_system_time_on_startup() {
        std::cout << "update_system_time_on_startup" << std::endl;
        receive_time_event(getCurrentTime());
    };
};

int main() {
    TimeEvent    te;
    std::jthread sync(&TimeEvent::wait, &te);

    for (int i = 0; i < 27; ++i) {
        std::this_thread::sleep_for(1s);
        std::cout << "." << std::flush;
    }

    te.shutdown();
}

在此处输入图像描述

BONUS2: Using Boost Thread interruption points

This has the advantage of keeping the TimeEvent instance encapsulated in the thread, thereby removing the need for manual sychronization. The disadvantage is requiring Boost Chrono, Boost System, Boost Thread (and in my example, also Boost Filesystem, just to show how easy that switch was):

Live On Coliru

#include <boost/chrono.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/thread.hpp>
#include <cassert>
#include <fstream>
#include <iostream>

#include <sys/stat.h>
#include <time.h>

namespace fs = boost::filesystem;

constexpr auto& clock_file     = "/home/saman/Desktop/clock";
constexpr auto& sync_file      = "/home/saman/Desktop/hi/hi/synchronized";
constexpr auto  cycle_interval = boost::chrono::seconds(5); // minutes(60)

class TimeEvent {
    static void touch(fs::path p) {
        create_directories(p.parent_path());
        fs::ofstream ofs(p);
        if (!ofs)
            throw std::runtime_error("Failed to create " + p.native());
    }
    static ::timespec getCurrentTime() {
        timespec currentTime;
        timespec_get(&currentTime, TIME_UTC);
        return currentTime;
    }

    using Clock = boost::chrono::steady_clock; // monotonous clock
    Clock::time_point const start = Clock::now();

    bool next_cycle() const {
        auto deadline = start;
        while (deadline <= Clock::now())
            deadline += cycle_interval;

        std::cout << "Waiting " << (deadline - Clock::now()) / boost::chrono::milliseconds(1) << "ms"
                  << std::endl;

        boost::this_thread::sleep_until(deadline);
        return true;
    };

  public:
    void shutdown() {
    }

    void receive_time_event(timespec /*time*/) {
        touch(clock_file);
        touch(sync_file);
    }

    timespec get_latest_clock_entry() { return getCurrentTime(); }

    void wait() {
        while (next_cycle()) {
            validate();
        }
    }

    void validate() {
        std::cout << "validate()" << std::endl;
        timespec currentTime  = getCurrentTime();
        timespec ModifiedTime = get_latest_clock_entry();

        if (currentTime.tv_sec > ModifiedTime.tv_sec)
            touch(clock_file);

        {
            // I don't want to risk messing up my systemclock while doing the testing
            auto tie = [](timespec const& ts) { return std::tie(ts.tv_sec, ts.tv_nsec); };
            assert(tie(currentTime) <= tie(ModifiedTime));
        }

        // update system time to match the clock file's modified time
        if (0 != clock_settime(CLOCK_REALTIME, &ModifiedTime))
            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
    }

    void update_system_time_on_startup() {
        std::cout << "update_system_time_on_startup" << std::endl;
        receive_time_event(getCurrentTime());
    };
};

void synchronizationThread() {
    try {
        TimeEvent timeEvent;
        timeEvent.update_system_time_on_startup();
        timeEvent.wait();
    } catch (std::exception const& e) {
        std::cerr << "synchronizationThread error: " << e.what() << std::endl;
    }
}

int main() {
    boost::thread sync(synchronizationThread);

    for (int i = 0; i < 27; ++i) {
        boost::this_thread::sleep_for(boost::chrono::seconds(1));
        std::cout << "." << std::flush;
    }

    if (sync.joinable()) {
        sync.interrupt();
        sync.join();
    }
}

You can even make it easier to never forget about interrupting and joining:

int main() {
    boost::thread sync(synchronizationThread);
    boost::thread_guard<boost::interrupt_and_join_if_joinable> guard(sync);

    for (int i = 0; i < 27; ++i) {
        boost::this_thread::sleep_for(boost::chrono::seconds(1));
        std::cout << "." << std::flush;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM