C99:限制指针记录线程安全?

时间:2011-07-16 16:34:38

标签: c concurrency c99 mutability restrict-qualifier

这个问题不是关于受限制的技术用法,而是关于主观用法的更多信息。虽然我可能会误解技术上的限制是多么有限,但在这种情况下,你应该随意烧我,以便在错误的前提下提出问题。

以下是我目前使用限制的两个例子:

如果我有一个指向一系列不可变字符的函数的函数,我不说它是受限制的,因为允许其他人在函数执行的同时通过他们自己的指针访问数据,例如从另一个并行线程。数据没有被修改,所以没问题。

但是,如果该函数采用指向它可能修改的可变字符序列的指针,我说它是受限制的,因为绝对不应该从任何指针访问数据(显然是函数使用的参数)由于可能不一致的数据而导致的函数执行。它还说明了数据被修改的可能性,因此编码器知道不会读取过时的数据,并且在访问时应该使用内存屏障等等......

我没有编写很多C代码,所以我很容易就错在这里假设的内容。这是对限制的正确用法吗?在这种情况下值得做吗?

我还假设一旦限制指针在函数返回时从堆栈中弹出,那么数据可以再次通过任何其他指针自由访问,并且限制仅持续与受限指针一样长。我知道这依赖于遵循规则的编码器,因为通过“非官方”指针访问受限数据是UB。

我有这一切吗?

编辑:

我只想说清楚我已经知道它绝对 nothing 以防止用户通过多个线程访问数据,而且我也知道C89不知道什么是'线程“甚至是。”

但是考虑到可以通过引用修改参数的任何上下文,很明显它不能在函数运行时被访问。这对强制执行线程安全没有任何作用,但它确实清楚地记录了您在执行函数期间通过自己的指针修改数据,风险自负

即使线程完全脱离了等式,你仍然允许在我认为正确的情况下进一步优化。

即便如此,感谢您迄今为止的所有权威答案。我是否赞成我喜欢的所有答案,或者仅仅是我接受的答案?如果接受不止一个怎么办?对不起,我是新来的,现在我会仔细查看常见问题...

4 个答案:

答案 0 :(得分:5)

restrict与线程安全无关。事实上,现有的C标准根本没有关于线程的话题;从规范的角度来看,没有“线程”这样的东西。

restrict是一种向编译器通知别名的方法。指针经常使编译器难以生成有效的代码,因为编译器在编译时无法知道两个指针是否实际引用相同的内存。玩具示例:

void foo(int *x, int *y) {
    *x = 5;
    *y = 7;
    printf("%d\n", *x);
}

当编译器处理此函数时,它不知道xy是否指向相同的内存位置。因此,它不知道它是否会打印57,并且必须在调用*x之前发出实际读取printf的代码。

但是如果你将x声明为int *restrict x,编译器可以证明此函数打印5,因此它可以提供编译时常量printf来电。{/ p>

使用restrict可以实现许多此类优化,尤其是在谈论数组操作时。

但这些与线程没有任何关系。要正确使用多线程应用程序,您需要适当的同步原语,如互斥,条件变量和内存障碍......所有这些都是特定于您的平台的,并且没有一个与restrict有任何关系。 / p>

[编辑]

要回答有关使用restrict作为文档形式的问题,我也会对此说“不”。

您似乎在考虑在变量可以或不能同时访问时记录。但是对于共享数据,适当的规则(几乎总是)确保它永远不会同时访问。

变量的相关文档是 它是否共享哪个互斥保护它。任何因任何原因访问该变量的代码,甚至只是为了读取它,都需要保存互斥锁。作者既不知道也不关心某个其他线程是否可能或者可能不会同时访问变量...因为其他线程将遵循相同的规则,并且互斥锁保证不存在并发访问。

这个学科很简单,有效,而且可以扩展,这就是为什么它是在线程之间共享数据的主要范例之一。 (另一个是消息传递。)如果你发现自己试图推理“这次我真的需要锁定互斥锁吗?”,你几乎肯定做错了什么。很难夸大这一点。

答案 1 :(得分:3)

不,我不认为这是一个很好的方言,提供有关来自不同线程的访问的任何信息。它的意思是关于指针的断言,它为处理的不同指针获得了特定的代码。线程不是语言的一部分。线程安全访问数据需要更多,restrict不是正确的工具。 volatile不是保证,您有时也会提出建议。

  • 互斥锁或其他锁定结构
  • 确保数据完整性的内存栅栏
  • 原子操作和类型

即将推出的标准C1x应该提供这样的结构。 C99不是。

答案 2 :(得分:1)

restrict的规范示例是memcpy() - OS X 10.6上的联机帮助页原型为:

void* memcpy(void *restrict s1, const void *restrict s2, size_t n);

memcpy中的源和目标区域不允许重叠;因此,通过将它们标记为restrict强制执行此限制 - 编译器可以知道源数组的任何部分都不与目标进行别名;它可以做一些事情,比如读取一大块来源,然后把它写进目的地。

本质上restrict是关于通过将指针标记为非别名来辅助编译器的优化 - 本身它不会帮助线程安全 - 它不会自动导致指向的对象被锁定被称为。

请参阅How to use the restrict qualified in Cwikipedia article on restrict进行更长时间的讨论。

答案 3 :(得分:1)

restrict向编译器提示,通过指针访问的缓冲区不会通过范围中的另一个指针进行别名。所以如果你有这样的功能:

foo(char *restrict datai, char *restrict dataj)
{
    // we've "promised" the compiler that it should not worry about overlap between 
    // datai and dataj buffers, this gives the compiler the opportunity to generate 
    // "better" code, potentially mitigating load-store issues, amongst other things
}

不使用restrict不足以在多线程应用程序中提供受保护的访问权限。如果多个线程通过char *参数以读/写方式同时访问共享缓冲区,则可能需要使用某种锁/互斥锁等 - 缺少restrict并不意味着线程安全

希望这会有所帮助。