C/Linux: 线程同步的使用

C/Linux: Uses of thread synchronization

我很难理解线程同步。我得到了以下线程函数:

void *thread_function(void *unused)
{
    long aux;

    for(int i = 0; i < 1000; i++){
        aux = count;
        aux++;
        usleep(random() % 10);
        count = aux;

    }

    return NULL;
}

count是一个初始化为0的全局变量。如果我运行这个函数有N个线程(比如4),count在值1000附近波动。

为什么会这样,count 的正确值应该是多少?如果我在 for 循环之前放置一个信号量 sem_wait,在 for 循环之后放置 sem_post,这是否意味着我的线程不再是 运行 并行?我应该在哪里放置 sem_waitsem_post 以便正确同步我的线程?

您假设操作是原子的。现在假设它们不是,实际系统就是这种情况。

作为一个全局变量,count可以被系统中的所有线程访问。这意味着,如果没有同步,所有线程将以非确定性和交错的方式执行以下操作:

for(int i = 0; i < 1000; i++){
    aux = count;
    aux++;
    usleep(random() % 10);
    count = aux;

}

总而言之,在循环的每次迭代中,每个线程都会在给定时刻拥有 count 值的副本,递增该副本 (aux++),然后 count 将被分配给本地值 count = aux;.

问题 1:每个线程读取的值 count 在线程执行时可能会因线程而异,因为一个线程可能正在读取一个值,而该值在某个时刻立即被另一个(或多个)线程修改之后(记住,操作不是原子的,可以以交错的方式执行)。

问题 2:分配给 count 的值不受任何锁定机制的保护,这意味着多个线程可能以交错方式甚至同时执行此指令(例如,在多处理器系统中,这是可能的)。这意味着执行的线程之一(您不知道它是什么)将 count 的值设置为 count = aux.

中的 aux

一个可能的执行场景的简单示例:

例如,假设三个线程。线程 1 读取值 count = 100 并被抢占。线程 2 读取值 100 并执行一段时间,将计数设置为(比方说)300,然后它被抢占。最后,线程 3 读取值 300 并执行一些循环迭代。如果线程 1 再次执行并设置值 count = aux,经过一个循环迭代后,该值将设置为 101。看到问题了!

需要同步来确保只有一个线程在执行读取、递增和赋值,实际上是为了使操作的行为就像它们是原子的一样。


问:如果我在 for 循环之前放一个信号量 sem_wait,在 for 循环之后放 sem_post,这是否意味着我的线程不再是 运行 并行?

A:也就是说每个线程都会交错执行for循环。例如,线程 1 将执行 for 循环的 100 次迭代,线程 2 将执行 200 次迭代,等等。请记住:调度程序控制每个线程的执行,因此迭代次数不受用户控制。您的代码已同步,但方式不理想。


问:我应该把 sem_waitsem_post 放在哪里才能正确同步我的线程?

A:您应该将信号量用于需要同步的尽可能少的操作,以便从 concurrent/parallel 代码执行中获得最大收益。例如,使用信号量,您的代码可以是:

for(int i = 0; i < 1000; i++){
    sem_wait(...);
        count++;
    sem_post(...);
}

您不再需要 aux,因为信号量确保只有一个线程递增 count.

的值

注意,当您使用线程时,您也可以使用互斥量而不是信号量。

我希望这能澄清你的疑虑。