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_wait
和 sem_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_wait
和 sem_post
放在哪里才能正确同步我的线程?
A:您应该将信号量用于需要同步的尽可能少的操作,以便从 concurrent/parallel 代码执行中获得最大收益。例如,使用信号量,您的代码可以是:
for(int i = 0; i < 1000; i++){
sem_wait(...);
count++;
sem_post(...);
}
您不再需要 aux
,因为信号量确保只有一个线程递增 count
.
的值
注意,当您使用线程时,您也可以使用互斥量而不是信号量。
我希望这能澄清你的疑虑。
我很难理解线程同步。我得到了以下线程函数:
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_wait
和 sem_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_wait
和 sem_post
放在哪里才能正确同步我的线程?
A:您应该将信号量用于需要同步的尽可能少的操作,以便从 concurrent/parallel 代码执行中获得最大收益。例如,使用信号量,您的代码可以是:
for(int i = 0; i < 1000; i++){
sem_wait(...);
count++;
sem_post(...);
}
您不再需要 aux
,因为信号量确保只有一个线程递增 count
.
注意,当您使用线程时,您也可以使用互斥量而不是信号量。
我希望这能澄清你的疑虑。