我们是否需要在单个编写器多读取器系统中锁定?

时间:2012-02-24 16:06:58

标签: c multithreading locking readerwriterlock

在os书中,他们说必须有一个锁,以保护数据不被读者和作者同时访问。 但是当我在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;
}

5 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

如果线程只是从global_array读取,它将正常工作。 printf应该没问题,因为这会在追加模式下执行单个IO操作。

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

例如,如果线程未进行同步,则在适当的情况下,它们可能永远不会看到global_array的任何更新。或者某些线程会比其他线程更快地看到更改。这一切都与内存页面刷新到中央内存以及线程何时看到其缓存中的更改有关。为了确保一致的结果,您需要同步(内存障碍)来强制缓存更新。

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

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

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

这涉及目标主机的架构,在某种程度上涉及编译器。

在您的示例中,您正在向数组写一个long。在这种情况下,问题是存储长原子?它可能是,但它取决于主机。 CPU可能会分别写出一部分长(上/下字/字节),因此读者可能会得到一个永远不会写的值。 (我认为,这在大多数现代CPU拱门上都不太可能,但你必须检查以确定。)

也可以在CPU中进行写缓冲。我看了这个已经有很长一段时间了,但我相信如果你没有必要的写屏障说明,可以让商店重新订购。从你的例子中不清楚你是否会依赖它。

最后,您可能需要将数组标记为volatile(再次,我有一段时间没有这样做,所以我对具体内容生锈了)以确保编译器不会假设数据不会在其下面发生变化。

答案 4 :(得分:0)

这取决于您对可移植性的关注程度。

至少在实际的英特尔x86处理器上,当您正在读/写双字对齐的双字(32位)数据时,硬件会“免费”为您提供原子性 - 即,您无需执行任何操作一种锁定来强制执行它。

更改任何内容(包括可能影响数据对齐的包含编译器标志)可能会破坏 - 但是可能会长时间隐藏(特别是如果您对特定数据的争用较少)项目)。它还会导致极其脆弱的代码 - 例如,切换到较小的数据类型可能会破坏代码,即使您只使用了值的子集。

当前的原子“保证”几乎是缓存和总线碰巧设计方式的偶然副作用。虽然我不确定我是否真的期待一个破坏事情的改变,但我不认为它也特别牵强。我见过这个原子行为文档的唯一地方是在相同的处理器手册中,这些手册涵盖了模型特定的寄存器,这些寄存器肯定已经从一个处理器模型改变(并继续改变)到下一个处理器模型。

最重要的是你真的应该进行锁定,但无论你测试多少,你都可能看不到当前硬件问题的表现(除非你改变错误对齐数据的条件)