2017-06-29 107 views
1

我处理多线程项目,C++,我怀疑有关的std ::互斥避免竞争条件下使用std ::互斥

让我们假设我有一个堆栈。

#include <exception> 
#include <memory> 
#include <mutex> 
#include <stack> 
struct empty_stack: std::exception 
{ 
    const char* what() const throw(); 
}; 
template<typename T> 
class threadsafe_stack 
{ 
private: 
    std::stack<T> data; 
    mutable std::mutex m; 
public: 
    threadsafe_stack(){} 
    threadsafe_stack(const threadsafe_stack& other) 
    { 
     std::lock_guard<std::mutex> lock(other.m); 
     data=other.data; 
    } 
    threadsafe_stack& operator=(const threadsafe_stack&) = delete; 
    void push(T new_value) 
    { 
     std::lock_guard<std::mutex> lock(m); 
     data.push(new_value); 
    } 
    std::shared_ptr<T> pop() 
    { 
     std::lock_guard<std::mutex> lock(m); 
     if(data.empty()) throw empty_stack(); 
     std::shared_ptr<T> const res(std::make_shared<T>(data.top())); 
     data.pop(); 
     return res; 
    } 
    void pop(T& value) 
    { 
     std::lock_guard<std::mutex> lock(m); 
     if(data.empty()) throw empty_stack(); 
     value=data.top(); 
     data.pop(); 
    } 
    bool empty() const 
    { 
     std::lock_guard<std::mutex> lock(m); 
     return data.empty(); 
    } 
}; 

有人说使用这个堆栈可以避免竞争条件。不过,我认为这里的问题是,这里的互斥体互斥只能保证单个功能不在一起。例如,我可以让线程调用push和pop。那些功能还存在竞争状况的问题。

例如:

threadsafe_stack st; //global varibale for simple 

void fun1(threadsafe_stack st) 
{ 

    std::lock_guard<std::mutex> lock(m); 
    st.push(t); 
    t = st.pop(); 
    // 
} 

void fun2(threadsafe_stack st) 
{ 
    std::lock_guard<std::mutex> lock(m); 
    T t,t2; 
    t = st.pop(); 
    // Do big things 
    st.push(t2); 

    // 
} 

如果一个线程FUN1和FUN2调用相同的堆(全局变量进行简单)。所以它可以是一个竞争条件(?)

我只有解决方案我可以认为是使用某种原子事务手段,而不是直接调用push(),pop(),empty(),我打电话给他们通过函数带有一个“函数指针”,这些函数只有一个互斥量。

例如:

#define PUSH 0 
#define POP  1 
#define EMPTY 2 

changeStack(int kindOfFunction, T* input, bool* isEmpty) 
{ 
    std::lock_guard<std::mutex> lock(m); 
    switch(kindOfFunction){ 
     case PUSH: 
      push(input); 
      break; 
     case POP: 
      input = pop(); 
      break; 
     case EMPTY: 
      isEmpty = empty(); 
      break;   
    } 
} 

是我的解决方案好?或者我只是过分追求,我的朋友告诉我的第一个解决方案就够了吗?有没有其他解决方案呢?解决方案可以避免像我所说的“原子事务”。

+1

为什么您认为如果从线程调用弹出/会有比赛条件?线程A调用push和阻塞互斥体,线程B调用pop并等待,直到互斥体被push解除阻塞。 – ForEveR

+0

你正试图解决一个没有问题。“互斥互斥这里只确保个人功能不在一起。” ---这是错误的假设。 –

回答

1

你误解了一些东西。你不需要那个changeStack函数。

如果你忘记lock_guard,这里是什么样子(与lock_guard,代码不相同,但lock_guard使它方便:使得解锁自动):

push() { 
    m.lock(); 
    // do the push 
    m.unlock(); 
} 

pop() { 
    m.lock(); 
    // do the pop 
    m.unlock(); 
} 

push被调用时,互斥量被锁定。现在,想象一下,在其他线程上,调用了poppop尝试锁定互斥锁,但它无法锁定它,因为push已将其锁定。所以它必须等待push解锁互斥锁。当push解锁互斥锁时,pop可以锁定它。

因此,简而言之,它是std::mutex哪些做互斥,而不是lock_guard

2

互斥锁是一个单锁,可以在任何时候由单个线程保存。 它不起作用。

因此,如果一个线程(T1)在push()的某个给定对象上持有锁,另一个线程(T2)将无法在pop()中获取它,并且在T1释放它之前将被阻塞。在释放T2的点(或另一个线程也被相同的互斥锁阻止)将被解除阻塞并允许继续。

您不需要在一个成员中完成所有锁定和解锁操作。

,您仍可以引入竞争条件的点是这样的构建体,如果它们出现在消费者代码:

if(!stack.empty()){ 
    auto item=stack.pop();//Guaranteed? 
} 

如果另一个线程T2进入pop()后线程T1进入empty()(上文)并被阻塞等待互斥锁,那么T1中的pop()可能会失败,因为T2'首先到达那里。除非其他同步正在处理它,否则可能会在empty()的末尾和该片段中的pop()的开始之间发生任何数量的操作。

在这种情况下,你应该想象T1 T2 &字面上赛车pop()但当然,他们可能会竞相不同成员,仍然无效对方...

如果你想建立一个像你的代码通常必须再添加原子成员函数,如try_pop(),如果堆栈为空,则返回(说)空的std::shared_ptr<>

我希望这句话是不是混淆:

锁定内部成员函数的对象互斥避免种族 条件之间的调用这些成员函数,但 调用这些成员函数之间不

解决该问题的最佳方法是通过添加执行多个“逻辑”操作的“复合”功能。这往往违背了良好的类设计,在这种设计中,您设计了一组逻辑的最小操作,并且耗费代码将它们组合在一起。

另一种方法是允许消耗代码访问互斥锁。例如公开void lock() const;void unlock() cont;成员。这通常是不可取的,因为(一)就变得非常容易了消费者的代码来创建死锁和(b)你要么使用递归锁(其开销),或再次翻倍成员函数:

void pop(); //Self locking version... 
void pop_prelocked(); //Caller must hold object mutex or program invalidated. 

无论你它们公开为publicprotected与否,这将使try_pop()是这个样子:

std::shared_ptr<T> try_pop(){ 
    std::lock_guard<std::mutex> guard(m); 
    if(empty_prelocked()){ 
     return std::shared_ptr<T>(); 
    } 
    return pop_prelocked(); 
} 

添加一个互斥锁,并在每个成员的开始收购这仅仅是故事的开始......

脚注:希望解释为mu tual ex lusion(mut **** ex)。还有一个围绕着潜伏在表面下方的成员障碍的整个其他主题,但如果您以这种方式使用互斥锁,则可以将其视为现在的实现细节...

+1

谢谢。我现在明白了。 –