代码之家  ›  专栏  ›  技术社区  ›  FooF

SMP体系结构中的pthread_create(3)和内存同步保证

  •  3
  • FooF  · 技术社区  · 11 年前

    我正在查看第4.11节 The Open Group Base Specifications Issue 7 (IEEE Std 1003.1, 2013 Edition), section 4.11 文档,其中详细说明了内存同步规则。这是我为详细描述POSIX/C内存模型而制定的POSIX标准中最具体的一个。

    这是一个报价

    4.11内存同步

    应用程序应确保对任何存储位置的访问 多个控制线程(线程或进程)受到限制 当 另一个控制线程可能正在修改它 限制使用同步线程执行的函数 相对于其他线程同步内存。以下内容 函数相对于其他线程同步内存:

    fork()pthread_barrier_wait()ptread_cond_broadcast() pthread_cond_signal()pthread-cond_timedwait()pturead_cond_wait() pthread_create()pthread_join(),pthread_mutex_lock() pthread_mutex_timedlock()

    pthread_mutex_trylock()pthread-mutex_unlock(),pthread_spin_lock() pthread_spin_trylock()pthread_spin_unlock()pturead_rwlock_rdlock() pthread_rwlock_timedrdlock()pthread_rwlock_ttimedwrlock() pthread_rwlock_tryrdlock()pthread_Rwllock_trywrlock()

    pthread_rwlock_unlock()pthread_rwlock_wrlock()sem_post() sem_timedwit()sem_trywait()sem_wait()semctl()semop()wait( waitpid()

    (要求除外)。

    基本上,重新解释上述文档,规则是当应用程序读取或修改内存位置而另一个线程或进程可能修改它时,他们应该确保 同步线程执行 和内存 相对于其他线程 通过调用列出的函数之一。其中, pthread_create(3) 以提供存储器同步。

    我明白这基本上意味着需要 memory barrier 每个函数都隐含着这个概念(尽管标准似乎没有使用这个概念)。例如,从 pthread_create() ,我们保证该线程在调用之前所做的内存修改会在其他线程(可能运行不同的CPU/内核)同步内存之后出现。但是,新创建的线程呢?在线程开始运行线程函数之前,是否存在隐含的内存障碍,以便它能够可靠地看到由 pthread_create() ? 这是标准规定的吗?或者我们应该显式地提供内存同步,以便能够信任根据POSIX标准读取的任何数据的正确性?

    特殊情况(这将作为一个特殊情况回答上述问题):上下文开关是否提供内存同步,即,当进程或线程的执行开始或恢复时,内存是否与其他执行线程的任何内存同步同步?

    例子:

    线 #1 创建从堆分配的常量对象。线 #1 创建新线程 #2 它从对象中读取数据。如果我们可以假设新线程 #2 从内存同步开始,然后一切正常。然而,如果运行新线程的CPU内核在其缓存内存中具有先前分配的但由于丢弃的数据的副本,而不是新值,那么它可能对状态有错误的看法,并且应用程序可能无法正常运行。

    更具体地说。。。

    1. 程序前一页(这是CPU#1缓存中的值)

       int i = 0;        
      
    2. 线 T0 磨合,磨合 CPU#0 :

       pthread_mutex_lock(...);
       int tmp = i;
       pthread_mutex_unlock(...);
      
    3. 线 T1 磨合,磨合 CPU#1 :

       i = 42;
       pthread_create(...);
      
    4. 新创建的线程 T2 磨合,磨合 CPU#0 :

       printf("i=%d\n", i);    /* First step in the thread function */
      

    无内存障碍,无同步线程 时间2 内存可能会发生输出

         i=0
    

    (以前缓存的未同步值)。

    更新: 如果允许这种疯狂的实现,许多使用POSIX线程库的应用程序将不会是线程安全的。

    3 回复  |  直到 11 年前
        1
  •  3
  •   nos    11 年前

    在线程开始运行线程函数之前是否存在隐含的内存障碍 可靠地看到pthread_create()同步的内存修改?

    对否则,pthread_create就没有意义充当内存同步(屏障)。

    (这是一个问题,posix没有明确说明( nor does posix define a standard memory model ), 所以你必须决定你是否信任你的实现做它可能做的唯一明智的事情——在新线程运行之前确保同步——我不会特别担心)。

    特殊情况(这将作为一个特殊情况回答上述问题):上下文开关是否提供内存同步,即,当进程或线程的执行开始或恢复时,内存是否与其他执行线程的任何内存同步同步?

    不,上下文开关不会起到屏障的作用。

    线程#1创建从堆分配的常量对象。线程#1创建一个新的线程#2,用于从对象中读取数据。如果我们可以假设新线程#2从内存同步开始,那么一切都很好。然而,如果运行新线程的CPU内核在其缓存内存中具有先前分配的但由于丢弃的数据的副本,而不是新值,那么它可能对状态有错误的看法,并且应用程序可能无法正常运行。

    由于pthread_create必须执行内存同步,因此这不会发生。任何驻留在另一个内核上的cpu缓存中的旧内存都必须失效。(幸运的是,常用的平台是缓存一致的,所以硬件会处理这一点)。

    现在,如果您更改对象 之后 你已经创建了你的2。线程,您需要再次进行内存同步,以便所有各方都能看到更改,否则将避免竞争条件。pthread互斥体通常用于实现这一点。

        2
  •  1
  •   Sigi    11 年前

    缓存一致性体系结构从体系结构设计的角度保证,即使是在访问内存位置时具有独立内存通道的分离CPU(ccNUMA-缓存一致性非统一内存体系结构),也不会导致示例中描述的不一致性。

    这是一个重要的惩罚,但应用程序将正常运行。

    线程#1在CPU0上运行,并将对象内存保存在缓存L1中。当CPU1上的线程#2读取相同的内存地址(或者更准确地说:相同的缓存行-查找 虚假分享 有关详细信息),它强制CPU0上的缓存未命中 在加载缓存行之前 .

        3
  •  0
  •   David Schwartz    11 年前

    你已经放弃了保证 pthread_create 提供了一个不连贯的。唯一的事情是 pthread_创建 函数可能做的是在调用它的线程和新创建的线程之间建立“发生在之前”的关系。

    它不可能与现有线程建立这样的关系。考虑两个线程,一个调用 pthread_创建 ,另一个访问共享变量。你能得到什么保证?“如果线程调用 pthread_创建 首先,保证另一个线程看到变量的最新值”。但“如果”使保证变得毫无意义和无用。

    正在创建线程:

    i = 1;
    pthread_create (...)
    

    创建的线程:

    if (i == 1)
       ...
    

    现在,这是一个连贯的保证——创建的线程必须看到 i 1 因为这“发生在”线程创建之前。我们的代码使得标准可以强制执行逻辑上的“先发生后发生”关系,而标准这样做是为了确保我们的代码按预期工作。

    现在,让我们尝试使用一个不相关的线程:

    正在创建线程:

    i=1;
    pthread_create(…)
    

    不相关线程:

    if ( i == 1)
        ...
    

    即使标准想要提供保证,我们还能有什么保证?由于线程之间没有同步,我们还没有尝试在关系之前实现逻辑发生。因此,标准不能尊重它——没有什么值得尊重的。没有特定的行为是“正确的”,因此标准无法向我们承诺正确的行为。

    这同样适用于其他功能。例如 pthread_mutex_lock 意味着获取互斥锁的线程可以看到任何解锁互斥锁的任何线程所做的或看到的所有更改。我们从逻辑上期望我们的线程在“之前”获得互斥锁的任何线程之后获得互斥锁,而标准承诺遵守这一期望,因此我们的代码可以工作。