【问题标题】:Order of execution of print with pthreads in LinuxLinux中使用pthreads打印的执行顺序
【发布时间】:2022-09-30 00:28:41
【问题描述】:

我正在使用 C,我想通过多线程在输出屏幕中获取字符串“ABCABCABCABCABCABC”。第一个线程显示\'A\' 字符,第二个线程显示\'B\',第三个线程显示\'C\'。如果我编译以下代码:

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

#define cantidad_impresion_letra 6
pthread_mutex_t semaforo = PTHREAD_MUTEX_INITIALIZER;

void *escribirA(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i++){
        pthread_mutex_lock(&semaforo);
        printf(\"A\");
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirB(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i++){
        pthread_mutex_lock(&semaforo);
        printf(\"B\");
        pthread_mutex_unlock(&semaforo);
    }
}

void *escribirC(void *unused){
    int i;
    for(i=0;i<cantidad_impresion_letra;i++){
        pthread_mutex_lock(&semaforo);
        printf(\"C\");
        pthread_mutex_unlock(&semaforo);
    }
}

int main(){
    pthread_t thread1, thread2, thread3;
    
    pthread_create(&thread1,NULL,escribirA,NULL);
    pthread_create(&thread2,NULL,escribirB,NULL);
    pthread_create(&thread3,NULL,escribirC,NULL);
        
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    
    return(0);
}

在通过 Dev-C++ 的 Windows 上,控制台会抛出我:ABACBACBACBACBACBC 但如果我编译相同Linux 中的代码,我得到CCCCCCBBBBBBAAAAAA
有人可以解释一下吗?

  • 使用多线程,一般来说你线程独立和并行工作。如果你想要一个特定的订单,那么使用线程,因为那样你会浪费资源。
  • 如果你必须做这个毫无意义的练习,请注意你只需要一个线程函数。将索引作为 pthread_create 参数传递。使用三个 condvar 或信号量,以便每个线程可以向链中的下一个发出信号。全局计数器/轮询解决方案是希望...\'非最优\'并获得较低的分数。

标签: c linux multithreading mutex


【解决方案1】:

有人可以解释一下吗?

linux 中的不同输出与使用的调度程序有关。

man sched

从 Linux 2.6.23 开始,默认调度程序是 CFS,即“完全公平调度程序”。

Completely Fair Scheduler:

每个 cfs_rq 类型的每个 CPU 运行队列排序sched_entity 结构时间顺序时尚成一棵红黑树(或 Linux 术语中的“rbtree”),其中最左边的节点被接收到的实体占据至少切片执行时间处理时间(保存在实体的 vruntime 字段中)。节点由处理器“执行时间”索引,以纳秒为单位。

这意味着什么:

每个线程运行得如此之快,以至于调度程序认为没有必要执行上下文切换。

只有 6 个迭代步骤和一个输出到缓冲流中(没有换行符,因此在刷新发生之前没有输出到终端)。

进行测试,增加迭代步骤,向输出中添加更多字符并添加换行符 (\n)。

顺序将开始改变。

在我的系统上,序列开始改变,有 200 个迭代步骤和 3 个字符(加上 \n)。


我正在使用 C,我想获取字符串“ABCABCABCABCABCABC”

1、顺序输出最好通过顺序执行来实现。

  • 只使用一个线程(单线程)

2.通知/等待

  • 每个线程执行一次迭代,向另一个线程发出信号,然后等待信号到达以便继续下一个周期

3. 屏障

//introduce NUM_THREAD lists
char dataA[cantidad_impresion_letra];
char dataB[cantidad_impresion_letra];
char dataC[cantidad_impresion_letra];

//let the thread do their work without any disturbance
void *escribirA(void *unused){ //unused could be used to pass the lists
    int i;
    for(i=0;i<cantidad_impresion_letra;i++){
        //pthread_mutex_lock(&semaforo); //not needed
        //instead of printf("A");
        dataA[i] = 'A';
        //pthread_mutex_unlock(&semaforo); //not needed
    }
}

//same for escribirB but write into dataB
//same for escribirC but write into dataC

int main()
{
    //...
    //after joining the threads
    //collective output in any desired order
    for (int i=0; i < cantidad_impresion_letra; ++i) {
        printf("%c", dataA[i]);
        printf("%c", dataB[i]);
        printf("%c", dataC[i]);
    }
    return 0;
}

优点:

  • 没有锁定/解锁(如果全局列表不被访问,否则)
  • 无等待和广播
  • 保证同步输出
  • 让操作系统决定何时最好产生线程(调度程序)

坏处:

  • 更多内存密集型(取决于线程数和迭代步骤)

【讨论】:

  • 关于调度的参考肯定很有趣,但我猜 OP 想要一个可移植的解决方案(至少在 Linux 和 Windows 上)来序列化线程执行。即使在多线程环境中序列化线程是不寻常的(如您所说),也可以使用用户空间 pthread API 来实现它。
  • @RachidK。同意,但 OP 也要求解释,我试图给出一个解释(这是一个操作系统的东西)。上述 cmets 中的用户 Cheatah 给出了不使用并行性的理由,用户 Rachid K. 提出了如何实现所需输出的解决方案。所以,我认为现在有足够的信息,OP 能够重新考虑实施(如果增加成本是值得的)。
  • 我同意你的看法:这最终是一个补充信息。
【解决方案2】:

在通过 Dev-C++ 的 Windows 上,控制台会抛出我:ABACBACBACBACBACBC 但如果我在 Linux 中编译相同的代码,我会得到 CCCCCCBBBBBBAAAAAA。 有人可以解释一下吗?

Linux 行为是您应该期待的。某些线程必须首先开始运行。无论是哪个线程,其他两个线程都会被阻塞。该线程将能够继续运行一段时间,因为它不需要等待。

您在 Windows 中看到的行为很糟糕——可能是最糟糕的。要么保持所有三个线程都安排好,要么没有。如果它让所有三个线程都安排好,那么它使用三个核心来完成仅用一个核心就能以同样快的速度完成的工作。如果它没有让所有三个线程都安排好,那么每个输出字符都会进行一次上下文切换,并且会浪费 CPU 时间不断填充代码和数据缓存,因为您刚刚从另一个任务切换。

【讨论】:

    【解决方案3】:

    您可以将执行顺序存储在名为例如的全局 volatile 变量中命令它被循环设置为值 0、1 和 2(由于增量和模运算)。使用条件变量检查此变量:

    #include <pthread.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    int order = 0;
    
    #define cantidad_impresion_letra 6
    pthread_mutex_t semaforo = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    
    void *escribirA(void *unused){
        int i;
        for(i=0;i<cantidad_impresion_letra;i++){
            pthread_mutex_lock(&semaforo);
            while (order != 0) {
              // Wake up another thread before going to sleep
              pthread_cond_signal(&cond);
              pthread_cond_wait(&cond, &semaforo);
            }
            printf("A");
            order = (order + 1) % 3;
            fflush(stdout);
            // Wake up another thread
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&semaforo);
        }
    }
    
    void *escribirB(void *unused){
        int i;
        for(i=0;i<cantidad_impresion_letra;i++){
            pthread_mutex_lock(&semaforo);
            while (order != 1) {
              // Wake up another thread before going to sleep
              pthread_cond_signal(&cond);
              pthread_cond_wait(&cond, &semaforo);
            }
            printf("B");
            order = (order + 1) % 3;
            fflush(stdout);
            // Wake up another thread
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&semaforo);
        }
    }
    
    void *escribirC(void *unused){
        int i;
        for(i=0;i<cantidad_impresion_letra;i++){
            pthread_mutex_lock(&semaforo);
            while (order != 2) {
              // Wake up another thread before going to sleep
              pthread_cond_signal(&cond);
              pthread_cond_wait(&cond, &semaforo);
            }
            printf("C");
            order = (order + 1) % 3;
            fflush(stdout);
            // Wake up another thread
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&semaforo);
        }
    }
    
    int main(){
        pthread_t thread1, thread2, thread3;
        
        pthread_create(&thread1,NULL,escribirA,NULL);
        pthread_create(&thread2,NULL,escribirB,NULL);
        pthread_create(&thread3,NULL,escribirC,NULL);
            
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        pthread_join(thread3, NULL);
        
        return(0);
    }
    

    执行示例:

    $ gcc order.c -o order -lpthread
    $ ./order
    ABCABCABCABCABCABC
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-05
      相关资源
      最近更新 更多