当前位置: 首页 > 知识库问答 >
问题:

C-克服原子操作的使用

邓星光
2023-03-14

我在想。如果我有一个int变量,我想在我所有的线程中同步-难道我不能保留一个位来知道值是否正在更新吗?
为了避免写入操作以块形式执行,这意味着线程可能正在访问中间写入值,这是不正确的,或者更糟糕的是,覆盖它,导致它完全错误,我希望线程首先被告知正在写入变量。我可以简单地使用一个原子操作来写入新值,这样其他线程就不会干涉,但是这个想法看起来并不那么愚蠢,我想先使用基本的工具。
如果我只做一个小操作呢足够让它保持在一个块中,一个像改变一个比特(这仍然会导致整个字节改变,但不是整个值改变,对吧)这样的操作,让这个比特指示变量是否被写入?这会起作用吗,还是整个int会被写入?
我的意思是,即使整个int要改变,这仍然有机会工作-如果指示值是否正在改变的位是先写的。

对此有什么想法吗?

编辑:我觉得我没有具体说明我实际上打算做什么,以及为什么我首先想到这个。
我试图实现一个超时函数,类似于JavaScript中的setTimeout。对于你不想取消的超时来说,这是非常简单的——你创建一个新线程,告诉它在给定的时间内Hibernate,然后给它一个要执行的函数,最终使用一些数据。小菜一碟。在大约半个小时内完成了它,而对C来说却是全新的。当你想设置一个超时时,困难的部分就来了,这个超时将来可能会被取消。因此,您在不取消超时的情况下执行与超时完全相同的操作,但是当线程唤醒并且CPU的调度程序将其打开时,线程必须检查启动时给出的内存中的值是否没有显示“您应该停止执行”。该值可能会被其他线程修改,但它只会被修改一次,至少在最好的情况下是这样。我将担心不同的解决方案,当它归结为试图修改值从多个线程在同一时间。现在的基本假设是,只有主线程或其他线程之一可以修改值,并且只发生一次。控制它只发生一次可以通过设置其他变量,这可能会改变多次,但总是相同的值(即,初始值为0,它意味着尚未取消,但当它必须取消时,值更改为1,所以不用担心值被分割成多个写入操作,并且在不同的线程读取它时只更新了其中的大部分)。
鉴于这个假设,我认为我最初在这篇文章开始时写的文本应该更清楚。简而言之,无需担心值被多次写入,仅一次,但由任何线程写入,并且该值必须可供任何其他线程读取,否则必须表示无法读取。
现在正如我我想到了,因为值本身只会是0或1,知道它什么时候已经被取消的技巧也应该起作用,不是吗?由于0或1将永远在一个操作中,因此无需担心它被碎片和读取错误。如果我错了,请纠正我。
另一方面,如果值是从结尾写的,而不是开头写的,那该怎么办?如果这是不可能的,那么没有必要担心,帖子会得到解决,但是我想知道在这个特定的背景下,克服这样的原子操作可能会带来的每一个危险。如果它是从末尾开始写的,并且线程想要访问变量以知道它是否应该继续执行,它会注意到它确实应该继续执行,而预期的行为是停止执行。这应该有完全最小的可能性,但仍然是,这意味着它是危险的,我希望它是100%可预测的。

另一个编辑来解释我想象的程序要执行的步骤
主线程生成一个新线程,也称为“可取消超时”。它传递一个要执行的函数以及数据、睡眠时间和内存地址,指向一个值。在给定时间后线程唤醒后,它必须检查该值,看看是否应该执行给定的函数。0表示它应该继续,1表示它应该停止并退出。该值(线程的“状态”,已取消或未取消)可由主线程或任何其他线程“超时”操作,该线程的任务是取消第一个线程
示例代码:

struct Timeout {
  void (*function)(void* data);
  void* data;
  int milliseconds;
  int** base;
  int cancelID;
};
DWORD WINAPI CTimeout(const struct Timeout* data) {
  Sleep(data->milliseconds);
  if(*(*(data->base) + sizeof(int) * data->cancelID) == 0) {
    data->function(data->data);
  }
  free(data);
  return 0;
}

其中,CTimeout是提供给新生成的线程的函数。请注意,我已经在go上编写了一些代码,但还没有对其进行测试。忽略任何潜在的错误
超时。base是指向int数组的指针,因为可以同时存在许多超时。超时。cancelID是超时列表中当前线程的ID。如果将同一ID视为基数组中的索引,则该ID具有一个值。如果值为0,则线程应执行其函数,否则,清理已提供的数据并返回。base是指针指向指针的原因是,在任何时候,都可以调整超时状态数组的大小。若数组的位置发生变化,则并没有传递其初始位置的选项。它可能会导致访问不再属于我们的内存时出现分段错误(如果不是,请纠正我)
如果需要,可以从主线程或其他线程访问Base,并且可以更改线程的状态以取消其执行
如果任何线程想要更改状态(状态为我们在开始时生成的超时状态,并希望取消),它应该更改“base”数组中的值。到目前为止,我认为这是非常简单的
如果“继续”和“停止”的值大于1字节,则会出现很大的问题。写入内存的操作实际上可能需要多次操作,因此,过早访问内存会导致出现意外结果,这不是我喜欢的。但是,正如我前面提到的,如果值非常小,0或1会怎么样?在什么时候访问值根本不重要吗?我们只对1个字节感兴趣,甚至2或4个字节或整数感兴趣,在这种情况下,即使8个字节也不会有任何区别,是吗?最后,不必担心接收到无效值,因为我们不关心32位的值,而只关心1位,不管我们将读取多少字节<也许我的意思并不完全可以理解。写/读操作不包括读取单个位,而是读取字节。也就是说,如果我们的值不大于255,或65535,或400万,无论我们正在写入/读取的字节数是多少,我们都不应该担心在写入过程中读取它。我们所关心的只是所写内容的一部分,即最后一个或第一个字节。其余的对我们来说完全没有用,所以不必担心在我们访问该值时会同步所有内容。真正的问题始于写入值时,但写入的第一个字节在末尾,这对我们来说是无用的。如果我们在那一刻读取该值,我们将收到我们不应该收到的-没有取消状态而不是取消。如果第一个字节,给定小endian,是先读取,我们将收到有效的价值,即使在读中间写。也许我把一切都弄糟了,弄错了。你知道,我不是职业选手。也许我一直在读一些无用的文章。如果我有什么错误,请纠正我。

共有1个答案

厉钊
2023-03-14

除了一些具有专用硬件的专用嵌入式环境外,没有“一个操作,它足够小,可以将其保存在一个块中,一个像改变一个位这样的操作”。您需要记住,您不希望简单地用“1”(或“0”)覆盖特殊位。因为即使你能做到这一点,它也可能与其他线程一样。事实上,您需要做的是检查它是否已经是1,并且仅当它不是自己编写1并且知道您没有覆盖现有1(或者由于1已经存在而编写1失败)时才进行检查。

这称为临界截面。而这个问题只能由操作系统来解决,操作系统恰好知道或能够阻止其他并行线程。这就是存在操作系统支持的同步方法的原因。

这是不容易的。

 类似资料:
  • 原子操作 是个不可分割的操作。 在系统的所有线程中,你是不可能观察到原子操作完成了一半这种情况的; 它要么就是做了,要么就是没做,只有这两种可能。 如果从对象读取值的加载操作是 原子 的,而且对这个对象的所有修改操作也是 原子 的, 那么加载操作得到的值要么是对象的初始值,要么是某次修改操作存入的值。 另一方面,非原子操作可能会被另一个线程观察到只完成一半。 如果这个操作是一个存储操作,那么其他线

  • 本文向大家介绍C#中使用Interlocked进行原子操作的技巧,包括了C#中使用Interlocked进行原子操作的技巧的使用技巧和注意事项,需要的朋友参考一下 什么是原子操作? 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。在C#中有多个线程同时对某个变量进行操作的时候,我们应该使用原子操作,防止多线

  • 我正在用C编写一个程序。为了简单起见,我们可以说:有几个变量,许多线程都可以读写。每次写入其中一个时,它都是通过原子交换(GCC原子操作、同步和交换)写入的。我是否需要在每次读取其中一个变量时使用原子负载,或者原子写入是否足以避免在写入过程中读取数据? 注意,需要使用其中一个变量的数据的任何位置都会首先复制值: 我的问题不是关于数据竞赛,也就是说我不担心我会丢失数据。我担心的是,在我阅读它的过程中

  • 主要内容:常用的原子操作命令维护原子性的推荐方法是保留所有相关信息,并将这些信息使用嵌入式文档的形式更新到文档中,这将确保单个文档的所有更新都是原子的。假设我们已经创建了一个名为 productDetails 的集合,并在其中插入了一个文档,如下所示: 在上面的文档中,我们将购买产品的客户的信息嵌入到 product_bought_by 字段中。当有新客户购买该产品时,我们首先会使用 product_available 字段

  • 乐观锁与悲观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。 在某个资源不可用的时候,就将

  • 对于并发操作而言,原子操作是个非常现实的问题。典型的就是i 的问题。 当两个CPU同时对内存中的i进行读取,然后把加一之后的值放入内存中,可能两次i的结果,这个i只增加了一次。 如何保证多CPU对同一块内存的操作是原子的。 golang中sync/atomic就是做这个使用的。 具体的原子操作在不同的操作系统中实现是不同的。比如在Intel的CPU架构机器上,主要是使用总线锁的方式实现的。 大致的