作者:2013丶峰峰最逆天_192 | 来源:互联网 | 2023-02-03 11:39
我有一个简单的问题:我需要创建三个线程并在每个线程中执行某个操作.第一个线程需要添加100
到array[0]
和减去101
从array[1]
,第二线程需要添加200
至array[1]
并减去201
从array[2]
而最终第三线程需要添加300
至array[2]
并减去301
从array[0]
.
以下是正确的解决方案,但运行时间非常长.如果我使用一个线程执行此任务,则运行时间将少于1秒,但是三个线程将运行时间增加到大约10秒(+ - 2秒).问题是什么?我认为有三个线程的解决方案必须更快.可能是我以错误的方式使用互斥?
#include
#include
#include
enum {SIZE = 3, ITER = 1000000};
double array[SIZE] = {};
pthread_t threads[SIZE];
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *func(void *arg) {
int n = *(int *)arg;
int tmp = 100 * (n + 1),
tmp2 = tmp + 1;
for (int i = 0; i != ITER; ++i) {
pthread_mutex_lock(&mutex);
array[n % SIZE] += tmp;
array[(n + 1) % SIZE] -= tmp2;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
int n[SIZE];
for (int i = 0; i != SIZE; ++i) {
n[i] = i;
pthread_create(threads + i, NULL, func, n + i);
}
for (int i = 0; i != SIZE; ++i) {
pthread_join(threads[i], NULL);
}
for (int i = 0; i != SIZE; ++i) {
printf("%.10g ", array[i]);
}
printf("\n");
return 0;
}
Liran Funaro..
5
虽然互斥锁带来了巨大的开销,但这并不是您放缓的原因.原因是互斥锁序列化执行.只有一个线程可以在任何时间点运行,因为只有一个线程可以保存互斥锁.所以你在实践中得到的是一个带有额外同步开销的串行执行.
您希望您的线程像这样运行(X
代表一个正在运行的线程).
Thread1: |X|X|X|X|X|
Thread2: |X|X|X|X|X|
Thread3: |X|X|X|X|X|
但相反,你得到的是这样的:
Thread1: |X| | |X| | |X| | |X| | |X| | |
Thread2: | |X| | |X| | |X| | |X| | |X| |
Thread3: | | |X| | |X| | |X| | |X| | |X|
在每个时隙,只有一个线程运行.
有几种方法:
您可以按照每个线程专门访问数组的不同索引的方式对操作进行分区.这样就可以避免使用互斥锁.但是,这需要对您的解决方案进行全面的重新设计.
您可以使每个线程在数组的本地副本上工作,然后在单个线程上组合所有线程的结果.这样做要简单得多,因为您只需将数据复制到线程中,即可省去互斥锁.
您可以为每个数组索引使用互斥锁,但由于高内存和时间开销,这似乎有点极端,因为冲突概率实际上非常低.
总而言之,使用选项1将产生最佳性能,但使用选项2将比串行版本产生显着的加速,而设计工作相对较少.选项3不明智.
1> Liran Funaro..:
虽然互斥锁带来了巨大的开销,但这并不是您放缓的原因.原因是互斥锁序列化执行.只有一个线程可以在任何时间点运行,因为只有一个线程可以保存互斥锁.所以你在实践中得到的是一个带有额外同步开销的串行执行.
您希望您的线程像这样运行(X
代表一个正在运行的线程).
Thread1: |X|X|X|X|X|
Thread2: |X|X|X|X|X|
Thread3: |X|X|X|X|X|
但相反,你得到的是这样的:
Thread1: |X| | |X| | |X| | |X| | |X| | |
Thread2: | |X| | |X| | |X| | |X| | |X| |
Thread3: | | |X| | |X| | |X| | |X| | |X|
在每个时隙,只有一个线程运行.
有几种方法:
您可以按照每个线程专门访问数组的不同索引的方式对操作进行分区.这样就可以避免使用互斥锁.但是,这需要对您的解决方案进行全面的重新设计.
您可以使每个线程在数组的本地副本上工作,然后在单个线程上组合所有线程的结果.这样做要简单得多,因为您只需将数据复制到线程中,即可省去互斥锁.
您可以为每个数组索引使用互斥锁,但由于高内存和时间开销,这似乎有点极端,因为冲突概率实际上非常低.
总而言之,使用选项1将产生最佳性能,但使用选项2将比串行版本产生显着的加速,而设计工作相对较少.选项3不明智.