I'm trying to optimise for consumer latency in an SPSC queue like this:
template <typename TYPE>
class queue
{
public:
    void produce(message m)
    {
        const auto lock = std::scoped_lock(mutex);
        has_new_messages = true;
        new_messages.emplace_back(std::move(m));
    }
    void consume()
    {
        if (UNLIKELY(has_new_messages))
        {
            const auto lock = std::scoped_lock(mutex);
            has_new_messages = false;
            messages_to_process.insert(
                messages_to_process.cend(),
                std::make_move_iterator(new_messages.begin()),
                std::make_move_iterator(new_messages.end()));
            new_messages.clear();
        }
        // handle messages_to_process, and then...
        messages_to_process.clear();
    }
private:
    TYPE has_new_messages{false};
    std::vector<message> new_messages{};
    std::vector<message> messages_to_process{};
    std::mutex mutex;
};
The consumer here is trying to avoid paying for locking/unlocking of the mutex if possible and does the check before locking the mutex.
The question is: do I absolutely have to use TYPE = std::atomic<bool> or I can save on atomic operations and reading a volatile bool is fine?
It's known that a volatile variable per se doesn't guarantee thread safety, however, std::mutex::lock() and std::mutex::unlock() provide some memory ordering guarantees. Can I rely on them to make changes to volatile bool has_new_messages to be eventually visible to the consumer thread outside of the mutex scope?
Update: Following @Peter Cordes' advice, I'm rewriting this as follows:
    void produce(message m)
    {
        {
            const auto lock = std::scoped_lock(mutex);
            new_messages.emplace_back(std::move(m));
        }
        has_new_messages.store(true, std::memory_order_release);
    }
    void consume()
    {
        if (UNLIKELY(has_new_messages.exchange(false, std::memory_order_acq_rel))
        {
            const auto lock = std::scoped_lock(mutex);
            messages_to_process.insert(...);
            new_messages.clear();
        }
    }
 
    