C/C++ - sem_t 类型的单个信号量按顺序打印数字

Posted

技术标签:

【中文标题】C/C++ - sem_t 类型的单个信号量按顺序打印数字【英文标题】:C/C++ - Single semaphore of type sem_t to print numbers in order 【发布时间】:2019-10-02 23:16:17 【问题描述】:

问题:假设我们有 n 个线程,每个线程接收一个介于 1 和 n 之间的随机唯一数。我们希望线程按排序顺序打印数字。

简单的解决方案(使用 n 个信号量/互斥量):我们可以使用 n 个互斥锁(或类似的信号量),其中线程 i 等待获取互斥锁编号 i 并解锁编号 i + 1。此外,线程 1 没有等待。

但是,我想知道是否可以模拟类似的逻辑使用单个信号量(类型为 sem_t)来实现以下逻辑:(i 是介于 1 到 n 之间的数字)

编号为 i 的线程作为输入,等待获取信号量上的 (i-1) 个计数,然后 打印,释放 i 的计数。不用说,线程一没有 等等。

我知道与 Java 不同,sem_t 不支持信号量值的任意增加/减少。此外,由于异步,编写一个 for 循环来执行 (i-1) 等待和 i 释放将无法正常工作。

我一直在寻找答案,但找不到任何答案。这在普通的C语言中可能吗?如果不是,是否可以在 C++ 中仅使用一个变量或信号量?总的来说,使用 ONE 信号量做到这一点最浪费的方法是什么。

由于我是多线程编程的新手,请随时编辑问题。

【问题讨论】:

这对于std::conditional_variable 来说是微不足道的。可以用吗? @selbie 我更喜欢一个也可以在纯 C 上工作的答案。不过,我也想知道您的解决方案。 告诉我们更多关于 i 的信息:我猜它是一个所有大于 0 的自然数的元素?你知道它的上限吗?我从你的问题中假设它是连续的?哪个 C++ 标准? C++20 好吗? 是的,n 是自然正数。而 i 只是一个从 1 到 n 的计数器。 如果你想用“C”来回答,为什么把问题标记为 C++? 【参考方案1】:

您可以使用 C++ 中的 condition_variable 执行此操作,这相当于使用 C 中的 pthreads 库的pthread_cond_t。

您希望在线程之间共享的是指向条件变量、数字和互斥锁的指针,以保护对数字的访问。

struct GlobalData

    std::condition_variable cv;
    int currentValue;
    std::mutex mut;
;

每个线程简单地调用一个函数,等待其编号被设置:

void WaitForMyNumber(std::shared_ptr<GlobalData> gd, int number)

    std::unique_lock<std::mutex> lock(gd->mut);
    while (gd->currentValue != number)
    
        gd->cv.wait(lock);
    

    std::cout << number << std::endl;
    gd->currentValue++;
    gd->cv.notify_all(); // notify all other threads that it can wake up and check

然后是一个程序来测试它。这个使用 10 个线程。您可以修改它以使用更多,然后拥有自己的数字列表随机化算法。

int main()

    int numbers[10] =  9, 1, 0, 7, 5, 3, 2, 8, 6, 4 ;
    std::shared_ptr<GlobalData> gd = std::make_shared<GlobalData>();
    // gd->number is initialized to 0.

    std::thread threads[10];

    for (int i = 0; i < 10; i++)
    
        int num = numbers[i];
        auto fn = [gd, num] WaitForMyNumber(gd, num); ;
        threads[i] = std::move(std::thread(fn));
    

    // wait for all the threads to finish
    for (int i = 0; i < 10; i++)
    
        threads[i].join();
    

    return 0;

以上所有内容都是用 C++ 编写的。但是使用pthreads 将上述解决方案转换为C 很容易。但我会把它留给 OP 做练习。

我不确定这是否满足您的“一个信号量要求”。互斥体在技术上具有信号量。不确定 condition_variable 本身是否有用于实现的信号量。

【讨论】:

谢谢,是的,这就是我要找的,我想我会自己试试 pthread 的。很好地使用 unique_ptr 来解锁互斥锁;)【参考方案2】:

这是一个很好的问题,但我担心您可能遇到 XY 问题,因为我无法想象您的问题场景的充分理由。尽管如此,在 1-2 分钟后,我想出了 2 个各有利弊的解决方案,但我认为一个对你来说是完美的:

A.当您的线程几乎同时完成并且/或者需要尽快打印时,您可以使用共享的std::atomic&lt;T&gt;T=unsigned,int,size_t,uint32_t,无论您喜欢什么,或者在使用 C 时使用 C 标准库中的任何整数原子,使用 C 初始化它0,现在我忙的每个线程都在等待,直到它的值为 i-1。如果是这样,它会打印然后在原子上添加 1。当然,由于繁忙的等待,当线程等待很长时间时,您将有很多 CPU 负载,而当许多线程等待时,您会减慢速度。但是你尽快得到你的印刷品

B.您只需将线程 i 的结果连同它的索引一起存储在一个容器中,因为我猜您想要更多只打印 i,并且在所有线程完成或定期完成后,对该容器进行排序,然后打印它。

答:

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <functional>

void thread_function(unsigned i, std::atomic<unsigned>& atomic) 
    while (atomic < i - 1) 
    std::cout << i << " ";
    atomic += 1;


int main() 
    std::atomic<unsigned> atomic = 0;

    std::vector<std::thread> threads;
    for (auto i : 3,1,2) 
        threads.push_back(std::thread(thread_function, i, std::ref(atomic)));
    
    for (auto& t : threads) 
        t.join();
    
    std::cout << "\n";

也可以在 C 中使用,只需使用那里的原子。

【讨论】:

谢谢。但我接受了另一个答案,因为我个人认为 while 循环有点浪费。顺便说一句,我认为它在 C 中不起作用。 atomic 是在 C++11 中引入的。 没有 C 从 C11 开始也有原子:en.cppreference.com/w/c/atomic【参考方案3】:

以下代码使用 pthread_cond_t 并在 C 中工作。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define n 100

int counter = 0;
int used[n];

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void foo(void *given_number)
    int number = (int)given_number;
    pthread_mutex_lock(&mutex);
    while(counter != number)
        pthread_cond_wait(&cond, &mutex);
    
    printf("%d\n", number);
    counter++;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);


int get_random_number()
    while(1)
        int x = rand()%n;
        if(!used[x])
            used[x] = 1;
            return x;
        
    


int main()
    pthread_t threads[n];
    for(int i = 0; i < n; i++)
        int num = get_random_number();
        pthread_create(&threads[i], NULL, foo, (void *)num);    
    
    for(int i = 0; i < n; i++)
        pthread_join(threads[i], NULL);
    
    return 0;

【讨论】:

以上是关于C/C++ - sem_t 类型的单个信号量按顺序打印数字的主要内容,如果未能解决你的问题,请参考以下文章

信号量

使用单个 `.vimrc`(不带 ftplugin)按文件类型更改 Vim 行为

sem_wait sem_post信号量操作进本函数

编写一个自定义函数,完成将3个字符按从小到大顺序输出

信号量sem 的用法

信号量,互斥锁,读写锁和条件变量的区别