16

有没有办法确保被阻塞的线程以与被阻塞相同的顺序被唤醒?我在某处读到这将被称为“强锁”,但我没有找到任何资源。

在 Mac OS X 上,可以设计一个 FIFO 队列来存储阻塞线程的所有线程 ID,然后使用漂亮的函数pthread_cond_signal_thread_np()来唤醒一个特定的线程——这显然是非标准和不可移植的。

我能想到的一种方法是使用类似的队列,然后unlock()将 a 发送broadcast()到所有线程并让它们检查哪一个是下一个。
但这会导致大量开销。

解决该问题的一种方法是将 packaged_task 发送到队列并让它按顺序处理它们。但这对我来说更像是一种解决方法而不是解决方案。

编辑:
正如评论所指出的,这个问题听起来可能无关紧要,因为原则上没有保证锁定尝试的顺序。
作为澄清:

我有一个我称之为 ConditionLockQueue 的东西,它与 Cocoa 库中的 NSConditionLock 类非常相似,但它维护一个阻塞线程的 FIFO 队列,而不是一个或多或少的随机池。

基本上任何线程都可以“排队”(有或没有特定“条件”的要求 - 一个简单的整数值 - 要满足)。然后线程被放置在队列中并阻塞,直到它是队列中满足条件的最前面的元素。

这提供了一种非常灵活的同步方式,我发现它在我的程序中非常有用。
现在我真正需要的是一种唤醒具有特定 id 的特定线程的方法。
但这些问题几乎是一样的。

4

3 回答 3

17

构建一个使用编号票证的锁对象非常容易,以确保其完全公平(按线程首先尝试获取它的顺序授予锁):

#include <mutex>
#include <condition_variable>

class ordered_lock {
    std::condition_variable  cvar;
    std::mutex               cvar_lock;
    unsigned int             next_ticket, counter;
public:
    ordered_lock() : next_ticket(0), counter(0) {}
    void lock() {
        std::unique_lock<std::mutex> acquire(cvar_lock);
        unsigned int ticket = next_ticket++;
        while (ticket != counter)
            cvar.wait(acquire);
    }
    void unlock() {
        std::unique_lock<std::mutex> acquire(cvar_lock);
        counter++;
        cvar.notify_all();
    }
};

编辑

修正奥拉夫的建议:

#include <mutex>
#include <condition_variable>
#include <queue>

class ordered_lock {
    std::queue<std::condition_variable *> cvar;
    std::mutex                            cvar_lock;
    bool                                  locked;
public:
    ordered_lock() : locked(false) {};
    void lock() {
        std::unique_lock<std::mutex> acquire(cvar_lock);
        if (locked) {
            std::condition_variable signal;
            cvar.emplace(&signal);
            signal.wait(acquire);
        } else {
            locked = true;
        }
    }
    void unlock() {
        std::unique_lock<std::mutex> acquire(cvar_lock);
        if (cvar.empty()) {
            locked = false;
        } else {
            cvar.front()->notify_one();
            cvar.pop();
        }
    }
};
于 2013-02-09T23:10:05.517 回答
0

我尝试了 Chris Dodd 解决方案 https://stackoverflow.com/a/14792685/4834897

但编译器返回错误,因为队列只允许有能力的标准容器。虽然引用 (&) 不可复制,但您可以在 Akira Takahashi 的以下回答中看到: https ://stackoverflow.com/a/10475855/4834897

所以我使用允许可复制引用的 reference_wrapper 更正了解决方案。

编辑: @Parvez Shaikh 建议通过在 lock() 函数中的 signal.wait() 之后移动 cvar.pop() 来进行小的改动,以使代码更具可读性

#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>
#include <vector>

#include <functional> // std::reference_wrapper, std::ref

using namespace std;

class ordered_lock {
    queue<reference_wrapper<condition_variable>> cvar;
    mutex                                        cvar_lock;
    bool                                         locked;
public:
    ordered_lock() : locked(false) {}
    void lock() {
        unique_lock<mutex> acquire(cvar_lock);
        if (locked) {
            condition_variable signal;
            cvar.emplace(std::ref(signal));
            signal.wait(acquire);
            cvar.pop();
        } else {
            locked = true;
        }
    }
    void unlock() {
        unique_lock<mutex> acquire(cvar_lock);
        if (cvar.empty()) {
            locked = false;
        } else {
            cvar.front().get().notify_one();
        }
    }
};

另一种选择是使用指针而不是引用,但它似乎不太安全。

于 2020-05-07T10:02:29.267 回答
-1

我们在这个线程上问正确的问题吗???如果是这样:他们的回答是否正确???

或者换一种说法:

我完全误解了这里的东西吗?

编辑段落:似乎StatementOnOrder(见下文)是错误的。请参阅链接 1 (Linux 下的 C++ 线程等通常基于 pthreads)和链接 2(将当前调度策略作为决定因素)——感谢来自cppreference ( ref ) 的Cubbi。另请参阅链接链接链接链接。如果该语句为假,那么拉原子(!)票的方法,如下面的代码所示,可能是首选!

开始...

StatementOnOrder:“多个线程遇到锁定的互斥体并因此按特定顺序“进入睡眠状态”,随后将获得互斥体的所有权并以相同的顺序继续运行。

问题:StatementOnOrder是真还是假???

void myfunction() {
    std::lock_guard<std::mutex> lock(mut);

    // do something
    // ...
    // mutex automatically unlocked when leaving funtion.
}

我问这个是因为迄今为止此页面上的所有代码示例似乎都是:

a) 浪费(如果StatementOnOrder为真)

或者

b) 严重错误(如果StatementOnOrder为假)。

那么,如果StatementOnOrder为假,为什么还要说他们可能“严重错误”呢?
原因是所有代码示例都认为他们通过使用 来超级聪明std::condition_variable,但实际上在此之前使用了锁,这会(如果StatementOnOrder为 false)弄乱了顺序!!!
只需在此页面上搜索std::unique_lock<std::mutex>,即可看到讽刺意味。

所以如果StatementOnOrder真的是假的,你就不能遇到锁,然后再处理票证和条件变量东西。相反,你必须做这样的事情:在遇到任何锁之前拉一张原子票!!!
为什么要在撞锁之前拉票?因为这里我们假设StatementOnOrder为假,所以任何排序都必须在“邪恶”锁之前完成。

#include <mutex>
#include <thread>
#include <limits>
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <map>

std::mutex mut;
std::atomic<unsigned> num_atomic{std::numeric_limits<decltype(num_atomic.load())>::max()};
unsigned num_next{0};
std::map<unsigned, std::condition_variable> mapp;

void function() {
    unsigned next = ++num_atomic; // pull an atomic ticket

    decltype(mapp)::iterator it;

    std::unique_lock<std::mutex> lock(mut);
    if (next != num_next) {
        auto it = mapp.emplace(std::piecewise_construct,
                               std::forward_as_tuple(next),
                               std::forward_as_tuple()).first;
        it->second.wait(lock);
        mapp.erase(it);
    }



    // THE FUNCTION'S INTENDED WORK IS NOW DONE
    // ...
    // ...
    // THE FUNCTION'S INDENDED WORK IS NOW FINISHED

    ++num_next;

    it = mapp.find(num_next); // this is not necessarily mapp.begin(), since wrap_around occurs on the unsigned                                                                          
    if (it != mapp.end()) {
        lock.unlock();
        it->second.notify_one();
    }
}

上面的函数保证了订单按照拉取的原子票执行。(编辑:使用 boost 的侵入式映射,将 condition_variable 保留在堆栈上(作为局部变量),将是一个很好的优化,可以在这里使用,以减少免费存储的使用!

但主要问题是:StatementOnOrder是真的还是假的???
(如果是真的,那么我上面的代码示例也是一种浪费,我们可以只使用互斥锁并完成它。)
我希望像Anthony Williams这样的人会检查这个页面......;)

于 2016-06-12T16:57:26.833 回答