【问题标题】:Is volatile variables safe in one customer thread and one producer thread? [duplicate]一个客户线程和一个生产者线程中的 volatile 变量是否安全? [复制]
【发布时间】:2016-04-10 05:12:25
【问题描述】:

这段代码正确吗?我在某人的博客中看到这段代码,它说 volatile 在只有一个客户和一个生产者的环境中是安全的。我不知道它是否真的是线程安全的。

代码如下:

#include <iostream>
#include <pthread.h>


template<class QElmType>
struct qnode
{
    struct qnode *next;
    QElmType data;
};
template<class QElmType>
class queue
{
public:
        queue() {init();}
        ~queue() {destroy();}

        bool init()
        {
                m_front = m_rear = new qnode<QElmType>;
                if (!m_front)
                        return false;
                m_front->next = 0;
                return true;
        }
        void destroy()
        {
                while (m_front)
                {
                        m_rear = m_front->next;
                        delete m_front;
                        m_front = m_rear;
                }
        }
        bool push(QElmType e)
        {
                struct qnode<QElmType> *p = new qnode<QElmType>;
                if (!p)
                        return false;
                p->next = 0;
                m_rear->next = p;
                m_rear->data = e;
                m_rear = p;
                return true;
        }
        bool pop(QElmType *e)
        {
                if (m_front == m_rear)
                        return false;


                struct qnode<QElmType> *p = m_front;
                *e = p->data;
                m_front = p->next;
                delete p;
                return true;
        }
private:
  struct qnode<QElmType> * volatile m_front, * volatile m_rear;
};


queue<int> g_q;


void *thread1(void * l)
{
        int i = 0;
        while (1)
        {
                g_q.push(i);
                i++;
                usleep(::rand()%1000);
        }
        return 0;
}
void *thread2(void * l)
{
        int i;
        while (1)
        {
                if (g_q.pop(&i))
                        std::cout << i << std::endl;
                //else
                        //std::cout << "can not pop" << std::endl;
                usleep(::rand()%1000);
        }
        return 0;
}


int main(int argc, char* argv[])
{
        pthread_t t1,t2;
        pthread_create(&t1, 0, thread1, 0);
        pthread_create(&t2, 0, thread2, 0);
        char ch;
        while (1)
        {
                std::cin >> ch;
                if (ch == 'q')
                        break;
        }
       return 0;
}

【问题讨论】:

标签: c++ c multithreading volatile


【解决方案1】:

没有。 volatile 不保证多线程安全。

注意标题,注意出处:

Volatile: Almost Useless for Multi-Threaded Programming

有一个普遍的观念认为关键字 volatile 有利于 多线程编程。我见过带有 volatile 的接口 限定词被证明为“它可能用于多线程 编程”。我一直认为它很有用,直到最近几周,当它 终于明白了我(或者如果你愿意,通过我厚厚的脑袋) volatile 对于多线程编程几乎没用。患病的 在这里解释为什么你应该从你的多线程中清除大部分内容 代码。

...

【讨论】:

    【解决方案2】:

    volatile 可以在有限的情况下工作,但不是你使用它的方式。


    您还有一两个错误,并且在初始化期间分配一个虚拟节点只会使事情复杂化:

    bool
    push(QElmType e)
    {
        struct qnode <QElmType> *p = new qnode <QElmType>;
    
        if (!p)
            return false;
    
        p->next = 0;
    
        // BUG: _not_ thread safe because multiple things being updated
        m_rear->next = p;
    
        // BUG: you want to set p->data here
        m_rear->data = e;
    
        m_rear = p;
    
        return true;
    }
    
    bool
    pop(QElmType *e)
    {
    
        if (m_front == m_rear)
            return false;
    
        struct qnode <QElmType> *p = m_front;
    
        *e = p->data;
        m_front = p->next;
        delete p;
    
        return true;
    }
    

    这是使用锁的清理代码。 注意:经过考虑,如果您尝试执行“环形队列”实现,我 [无意中] 将其简化为非循环列表。我更关心锁定。即使使用原始版本,仍然需要锁定

    bool
    init()
    {
    
        m_front = m_rear = nullptr;
    
        return true;
    }
    
    bool
    push(QElmType e)
    {
        struct qnode <QElmType> *p = new qnode <QElmType>;
    
        if (! p)
            return false;
    
        p->next = 0;
        p->data = e;
    
        // with the lock, now the _multiple_ can be updated _atomically_
        lock();
    
        if (m_front == nullptr)
            m_front = p;
    
        if (m_rear != nullptr)
            m_rear->next = p;
    
        m_rear = p;
    
        unlock();
    
        return true;
    }
    
    bool
    pop(QElmType *e)
    {
        bool valid;
    
        lock();
    
        struct qnode <QElmType> *p = m_front;
    
        valid = (p != nullptr);
    
        if (valid) {
            *e = p->data;
    
            m_front = p->next;
            if (p == m_rear)
                m_rear = m_front;
    
            delete p;
        }
    
        unlock();
    
        return valid;
    }
    

    volatile 的简单有效用法是:

    volatile int stopflg;
    
    void *
    thread1(void *l)
    {
        while (! stopflg) {
            // ...
        }
    
        return 0;
    }
    
    void *
    thread2(void *l)
    {
        while (! stopflg) {
            //...
        }
    
        return 0;
    }
    
    int
    main(int argc, char *argv[])
    {
        pthread_t t1,
         t2;
    
        pthread_create(&t1, 0, thread1, 0);
        pthread_create(&t2, 0, thread2, 0);
    
        char ch;
    
        while (1) {
            std::cin >> ch;
            if (ch == 'q') {
                stopflg = 1;
                break;
            }
        }
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      volatile 是一个编译器指令,表示变量可以随时更改。这是为了防止编译器进行会导致错误的优化。在为内存映射的硬件编写 C 代码时很有用 - 设备上的 I/O 可以更改变量的状态。

      这真的与编写多线程代码无关。

      Why is volatile needed in C?

      【讨论】:

      • volatile 不是编译器指令。它是核心语言的一部分。
      • 是的,它是:"arduino.cc/en/Reference/Volatile" "声明变量 volatile 是对编译器的指令" 注意我没有说它是预处理器指令。
      最近更新 更多