当多个线程循环运行时索引如何受到影响

2024-01-02

我试图编写一个运行 5 个线程并相应地打印其索引的程序。

下面是代码:

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

int nthreads=5;

void *busy(void* c) {

    int my_busy = *(int *) c;

    printf("Hello World with thread index %d\n", my_busy);

    return NULL;
}    

int main()
{
    pthread_t t1[nthreads];
    void* result;

    for(int i=0; i<nthreads; i++)
    {
        pthread_create(&t1[i], NULL, busy, &i);
    }

    for(int i=0; i<nthreads; i++)
    {
        pthread_join(t1[i], &result);
    }
    return 0;
}

获得的输出:

Hello World with thread index 1
Hello World with thread index 4
Hello World with thread index 2
Hello World with thread index 0
Hello World with thread index 0

虽然5个线程都运行了,为什么相应的索引没有正确输出?为什么我倾向于丢失一些索引而将其他索引获取两次?例如,在本例中,我丢失了 3,并且输出了 0 两次。 虽然使用pthread_join随着pthread_create在一个循环中解决了它不安排所有线程并行运行的问题。在这种情况下应该怎么做才能打印所有索引?


虽然5个线程都运行了,为什么相应的索引没有正确输出?

您将指向变量的指针传递给每个线程,并在线程函数访问该变量的同时修改该变量。为什么您期望线程函数看到任何特定值?他们同时运行。在某些体系结构上,如果一个线程正在读取该值,而另一个线程正在修改该值,则线程可能会看到完全不可能的乱码值。

例如,在本例中,我丢失了 3,并且输出了 0 两次。

虽然由例如生成的机器代码GCC 在创建每个线程函数后递增线程函数访问的变量,在某些体系结构上线程函数观察到的值可能是“旧的”,因为没有使用屏障或同步。

这是否发生在您的特定机器上(没有明确的障碍或同步),取决于哪个内存排序模型 https://en.wikipedia.org/wiki/Memory_ordering你的机器实现了。


例如,在 x86-64(又名 AMD64;64 位 Intel/AMD 架构)上,所有读取和写入均按顺序观察,除了商店可以在装载后订购。这意味着如果最初说i = 0;,线程 A 执行此操作i = 1;,线程B仍然可以看到i == 0即使在线程 A 修改了变量之后。

请注意,添加障碍(例如_mm_fence()使用 x86/AMD64 提供的内在函数<immintrin.h>当使用大多数 C 编译器时)不足以确保每个线程看到唯一的值,因为每个线程的启动可能会相对于现实世界的时刻有所延迟pthread_create()被称为。他们所确保的是至多一个线程看到零值。两个线程可能看到值 1,三个线程可能看到值 2,依此类推;甚至所有线程都可能看到值 5。

在这种情况下应该怎么做才能打印所有索引?

最简单的选项是提供要打印的索引作为值,而不是作为指向变量的指针。在 busy() 中,使用

my_busy = (int)(intptr_t)c;

在 main() 中,

pthread_create(&t1[i], NULL, busy, (void *)(intptr_t)i);

The intptr_ttype 是能够保存指针的有符号整数类型,并在中定义<stdint.h>(通常通过包含<inttypes.h>反而)。

(由于问题被标记为linux /questions/tagged/linux,我可能应该指出,在 Linux 中,在所有架构上,您都可以使用long代替intptr_t, and unsigned long代替uintptr_t。两者都没有陷阱表示long or unsigned long,以及一切可能的long/unsigned long值可以转换为唯一的void *,反之亦然;保证往返工作正常。内核系统调用接口要求这样做,因此将来也极不可能改变。)


如果您需要将指针传递给i,但希望每个线程看到唯一的值,您需要使用某种同步。

最简单的同步方法是使用信号量。您可以将其设置为全局,但使用结构来描述工作参数并传递该结构的指针(即使所有工作线程使用相同的指针)会更稳健:

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

#define  NTHREADS  5

struct work {
    int     i;
    sem_t   s;
};

void *worker(void *data)
{
    struct work *const  w = data;
    int                 i;

    /* Obtain a copy of the value. */
    i = w->i;

    /* Let others know we have copied the value. */
    sem_post(&w->s);

    /* Do the work. */
    printf("i == %d\n", i);
    fflush(stdout);

    return NULL;
}    

int main()
{
    pthread_t    thread[NTHREADS];
    struct work  w;
    int          rc, i;

    /* Initialize the semaphore. */
    sem_init(&w.s, 0, 0);

    /* Create the threads. */
    for (i = 0; i < NTHREADS; i++) {

        /* Create the thread. */
        w.i = i;
        rc = pthread_create(&thread[i], NULL, worker, &w);
        if (rc) {
            fprintf(stderr, "Failed to create thread %d: %s.\n", i, strerror(rc));
            exit(EXIT_FAILURE);
        }

        /* Wait for the thread function to grab its copy. */
        sem_wait(&w.s);
    }

    /* Reap the threads. */
    for (i = 0; i < NTHREADS; i++) {
        pthread_join(thread[i], NULL);
    }

    /* Done. */
    return EXIT_SUCCESS;
}

因为主线程(修改每个工作线程看到的值的线程)参与了同步,因此每个工作函数在创建下一个线程之前读取该值,因此输出将始终按升序排列i.


更好的方法是创建一个工作池,其中主线程定义了线程要共同完成的工作,并且线程函数只需以任何顺序获取下一个要完成的工作块:

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>

#define  NTHREADS  5
#define  LOOPS     3

struct work {
    pthread_mutex_t  lock;
    int              i;
};

void *worker(void *data)
{
    struct work *const  w = data;
    int                 n, i;

    for (n = 0; n < LOOPS; n++) {

        /* Grab next piece of work. */
        pthread_mutex_lock(&w->lock);
        i = w->i;
        w->i++;
        pthread_mutex_unlock(&w->lock);

        /* Display the work */
        printf("i == %d, n == %d\n", i, n);
        fflush(stdout);
    }

    return NULL;
}

int main(void)
{
    pthread_attr_t  attrs;
    pthread_t       thread[NTHREADS];
    struct work     w;
    int             i, rc;

    /* Create the work set. */
    pthread_mutex_init(&w.lock, NULL);
    w.i = 0;

    /* Thread workers don't need a lot of stack. */
    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);

    /* Create the threads. */
    for (i = 0; i < NTHREADS; i++) {
        rc = pthread_create(thread + i, &attrs, worker, &w);
        if (rc != 0) {
            fprintf(stderr, "Error creating thread %d of %d: %s.\n", i + 1, NTHREADS, strerror(rc));
            exit(EXIT_FAILURE);
        }
    }

    /* The thread attribute set is no longer needed. */
    pthread_attr_destroy(&attrs);

    /* Reap the threads. */
    for (i = 0; i < NTHREADS; i++) {
        pthread_join(thread[i], NULL);
    }

    /* All done. */
    return EXIT_SUCCESS;
}

如果编译并运行最后一个示例,您会注意到输出可能是奇数顺序,但每个i都是独一无二的,并且每一个n = 0通过n = LOOPS-1恰好发生NTHREADS times.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当多个线程循环运行时索引如何受到影响 的相关文章

  • 仅使用containerd(不使用Docker)修剪容器镜像

    如果我刚刚containerd安装在 Linux 系统上 即 Docker 是not安装 如何删除未使用的容器映像以节省磁盘空间 Docker 就是这么方便docker system prune https docs docker com
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • C++11 删除重写方法

    Preface 这是一个关于最佳实践的问题 涉及 C 11 中引入的删除运算符的新含义 当应用于覆盖继承父类的虚拟方法的子类时 背景 根据标准 引用的第一个用例是明确禁止调用某些类型的函数 否则转换将是隐式的 例如最新版本第 8 4 3 节
  • 如何从 Visual Studio 将视图导航到其控制器?

    问题是解决方案资源管理器上有 29 个项目 而且项目同时具有 ASP NET MVC 和 ASP NET Web 表单结构 在MVC部分中 Controller文件夹中有大约100个子文件夹 每个文件夹至少有3 4个控制器 视图完全位于不同
  • 随着时间的推移,添加到 List 变得非常慢

    我正在解析一个大约有 1000 行的 html 表 我从一个字符串中添加 10 个字符串 td 每行到一个list td
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 传递给函数时多维数组的指针类型是什么? [复制]

    这个问题在这里已经有答案了 我在大学课堂上学习了 C 语言和指针 除了多维数组和指针之间的相似性之外 我认为我已经很好地掌握了这个概念 我认为由于所有数组 甚至多维 都存储在连续内存中 因此您可以安全地将其转换为int 假设给定的数组是in
  • 访问外部窗口句柄

    我当前正在处理的程序有问题 这是由于 vista Windows 7 中增强的安全性引起的 特别是 UIPI 它阻止完整性级别较低的窗口与较高完整性级别的窗口 对话 就我而言 我想告诉具有高完整性级别的窗口进入我们的应用程序 它在 XP 或
  • 重载 (c)begin/(c)end

    我试图超载 c begin c end类的函数 以便能够调用 C 11 基于范围的 for 循环 它在大多数情况下都有效 但我无法理解和解决其中一个问题 for auto const point fProjectData gt getPoi
  • Linux 中 m 标志和 o 标志将存储在哪里

    我想知道最近收到的路由器通告的 m 标志和 o 标志的值 从内核源代码中我知道存储了 m 标志和 o 标志 Remember the managed otherconf flags from most recently received R
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • ASP.NET Core 3.1登录后如何获取用户信息

    我试图在登录 ASP NET Core 3 1 后获取用户信息 如姓名 电子邮件 id 等信息 这是我在登录操作中的代码 var claims new List
  • 如何在当前 Visual Studio 主机内的 Visual Studio 扩展中调试使用 Roslyn 编译的代码?

    我有一个 Visual Studio 扩展 它使用 Roslyn 获取当前打开的解决方案中的项目 编译它并从中运行方法 程序员可以修改该项目 我已从当前 VisualStudioWorkspace 成功编译了 Visual Studio 扩
  • C 函数 time() 如何处理秒的小数部分?

    The time 函数将返回自 1970 年以来的秒数 我想知道它如何对返回的秒数进行舍入 例如 对于100 4s 它会返回100还是101 有明确的定义吗 ISO C标准没有说太多 它只说time 回报 该实现对当前日历时间的最佳近似 结
  • 使用特定参数从 SQL 数据库填充组合框

    我在使用参数从 sql server 获取特定值时遇到问题 任何人都可以解释一下为什么它在 winfom 上工作但在 wpf 上不起作用以及我如何修复它 我的代码 private void UpdateItems COMBOBOX1 Ite
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • C# 使用“?” if else 语句设置值这叫什么

    嘿 我刚刚看到以下声明 return name null name NA 我只是想知道这在 NET 中叫什么 是吗 代表即然后执行此操作 这是一个俗称的 条件运算符 三元运算符 http en wikipedia org wiki Tern
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么
  • 现代编译器是否优化乘以 1 和 -1

    如果我写 template

随机推荐