0

在操作系统书籍中,他们说必须有一个锁来保护数据不被读写器同时访问。但是当我在 x86 机器上测试这个简单的例子时,它运行良好。我想知道,这里的锁是必需的吗?

#define _GNU_SOURCE
#include <sched.h>

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


struct doulnum
{
 int i;
 long int l;
 char c;
 unsigned int ui;
 unsigned long int ul;
 unsigned char uc;
};


long int global_array[100] = {0};


void* start_read(void *_notused)
{
 int i;
 struct doulnum d;
 int di;
 long int dl;
 char dc;
 unsigned char duc;
 unsigned long dul;
 unsigned int dui;
 while(1)
    {
     for(i = 0;i < 100;i ++)
        {
         dl = global_array[i];
         //di = d.i;
         //dl = d.l;
         //dc = d.c;
         //dui = d.ui;
         //duc = d.uc;
         //dul = d.ul;
         if(dl > 5 || dl < 0)
            printf("error\n");
         /*if(di > 5 || di < 0 || dl > 10 || dl < 5)
            {
             printf("i l value %d,%ld\n",di,dl);
             exit(0);
            }
         if(dc > 15 || dc < 10 || dui > 20 || dui < 15)
            {
             printf("c ui value %d,%u\n",dc,dui);
             exit(0);
            }
         if(dul > 25 || dul < 20 || duc > 30 || duc < 25)
            {
             printf("uc ul value %u,%lu\n",duc,dul);
             exit(0);
            }*/
        }
    }
}


int start_write(void)
{
 int i;
 //struct doulnum dl;
 while(1)
    {
     for(i = 0;i < 100;i ++)
        {
         //dl.i = random() % 5;
         //dl.l = random() % 5 + 5;
         //dl.c = random() % 5 + 10;
         //dl.ui = random() % 5 + 15;
         //dl.ul = random() % 5 + 20;
         //dl.uc = random() % 5 + 25;
         global_array[i] = random() % 5;
        }
    }
 return 0;
}


int main(int argc,char **argv)
{
 int i;
 cpu_set_t cpuinfo;
 pthread_t pt[3];
 //struct doulnum dl;
 //dl.i = 2;
 //dl.l = 7;
 //dl.c = 12;
 //dl.ui = 17;
 //dl.ul = 22;
 //dl.uc = 27;
 for(i = 0;i < 100;i ++)
    global_array[i] = 2;
 for(i = 0;i < 3;i ++)
     if(pthread_create(pt + i,NULL,start_read,NULL) < 0)
        return -1;
/* for(i = 0;i < 3;i ++)
        {
         CPU_ZERO(&cpuinfo);
         CPU_SET_S(i,sizeof(cpuinfo),&cpuinfo);
         if(0 != pthread_setaffinity_np(pt[i],sizeof(cpu_set_t),&cpuinfo))
                {
                 printf("set affinity %d\n",i);
                 exit(0);
                }
        }
 CPU_ZERO(&cpuinfo);
 CPU_SET_S(3,sizeof(cpuinfo),&cpuinfo);
 if(0 != pthread_setaffinity_np(pthread_self(),sizeof(cpu_set_t),&cpuinfo))
        {
         printf("set affinity recver\n");
         exit(0);
        }*/
 start_write();
 return 0;
}
4

5 回答 5

1

如果不同步读取和写入,读取器可以写入器写入时读取,如果写入操作不是原子的,则读取处于半写入状态的数据。所以是的,为了防止这种情况发生,同步是必要的。

于 2012-02-24T16:10:04.797 回答
0

这取决于您对便携性的关心程度。

至少在实际的 Intel x86 处理器上,当您读取/写入也是双字对齐的双字(32 位)数据时,硬件“免费”为您提供原子性 - 即,您无需进行任何类型的锁定强制执行。

更改大部分内容(最多包括可能影响数据对齐的编译器标志)可能会破坏这一点 - 但可能会长时间保持隐藏状态(特别是如果您对特定数据项的争用较少)。它还会导致代码极其脆弱——例如,切换到较小的数据类型可能会破坏代码,即使您只使用值的子集。

当前的原子“保证”几乎是缓存和总线设计方式的意外副作用。虽然我不确定我是否真的期待一个会破坏事情的变化,但我也不会认为它特别牵强。我唯一看到这种原子行为的文档是在同一个处理器手册中,这些手册涵盖了特定于模型的寄存器之类的内容,这些寄存器肯定已经从一种处理器模型更改为下一个处理器模型。

底线是您确实应该进行锁定,但是无论您进行多少测试,您都可能不会看到当前硬件问题的表现(除非您更改诸如未对齐数据之类的条件)。

于 2012-02-24T17:18:53.323 回答
0

你肯定需要在这里同步。原因很简单,当 start_write 更新全局数组中的信息并且您的 3 个线程之一尝试从全局数组中读取相同的数据时,数据处于不一致状态的明显可能性。您引用的内容也不正确。“必须是保护数据不被读写器同时访问的锁”应该是“必须是保护数据不被读写器同时修改的锁”

如果共享数据正在被其中一个线程修改,而另一个线程正在从中读取,则需要使用锁来保护它。

如果共享数据被两个或更多线程访问,那么您不需要保护它。

于 2012-02-24T16:21:51.080 回答
0

一般的答案是您需要某种方法来确保/强制执行必要的原子性,因此读者不会看到不一致的状态。

锁(正确完成)就足够了,但并不总是必要的。但是为了证明它没有必要,您需要能够说明所涉及操作的原子性。

这既涉及目标主机的体系结构,又在某种程度上涉及编译器。

在您的示例中,您正在向数组写入 long 。在这种情况下,问题是存储一个长原子?可能是,但这取决于主机。CPU 可能会分别写出长(高/低字/字节)的一部分,因此读者可以获得从未写入的值。(我相信,这在大多数现代 CPU 架构上不太可能发生,但您必须检查以确定。)

CPU 中也可能存在写缓冲。我已经很久没有看到这个了,但我相信如果你没有必要的写屏障指令,就有可能获得商店重新排序。从你的例子中不清楚你是否会依赖这个。

最后,您可能需要将数组标记为volatile(同样,我有一段时间没有这样做了,所以我对细节生疏了),以确保编译器不会对数据不发生变化做出假设在它下面。

于 2012-02-24T16:24:08.833 回答
0

如果线程只是从global_array. printf应该没问题,因为这会在附加模式下执行单个 IO 操作。

但是,由于主线程同时调用start_write更新global_array其他线程,start_read因此它们将以非常不可预测的方式读取值。这在很大程度上取决于线程在操作系统中的实现方式、您拥有多少 CPU/内核等。这可能在您的双核开发机器上运行良好,但当您迁移到 16 核生产服务器时,它会严重失败。

例如,如果线程没有同步,他们可能永远不会global_array在正确的情况下看到任何更新。或者某些线程会比其他线程更快地看到更改。这完全取决于内存页面何时刷新到中央内存以及线程何时看到其缓存中的更改。为确保结果一致,您需要同步(内存屏障)以强制更新缓存。

于 2012-02-24T16:17:32.730 回答