编辑问题:是否可以通过线程安全访问位数组?我在下面的实现似乎需要互斥锁,这违背了并行化的目的。
我的任务是使用pthreads创建一个双元素生成器的并行实现。我决定使用Eratosthenes的Sieve并划分标记已知质数因子的工作。我错开了一个线程得到的因素。
例如,如果有4个线程: 线程一标记倍数3,11,19,27 ...... 线程二标记倍数5,13,21,29 ...... 线程二标记倍数7,15,23,31 ...... 线程二标记倍数9,17,25,33 ......
我跳过偶数倍数和偶数基数。我使用过bitarray,所以我把它运行到INT_MAX。我遇到的问题是最大值为1000万,结果大约有5个数字,这是与已知文件相比有多少错误。结果一直变化到大约10000的最大值,其中它变化1个数字。下面的任何内容都没有错误。
起初我并不认为流程之间需要沟通。当我看到结果时,我添加了一个pthread屏障,让所有线程在每组倍数之后赶上。这没有任何改变。 在mark()函数周围添加一个互斥锁可以解决问题,但这会减慢一切。
这是我的代码。希望有人可能会看到明显的东西。
#include <pthread.h>
#include <stdio.h>
#include <sys/times.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <limits.h>
#include <getopt.h>
#define WORDSIZE 32
struct t_data{
int *ba;
unsigned int val;
int num_threads;
int thread_id;
};
pthread_mutex_t mutex_mark;
void mark( int *ba, unsigned int k )
{
ba[k/32] |= 1 << (k%32);
}
void mark( int *ba, unsigned int k )
{
pthread_mutex_lock(&mutex_mark);
ba[k/32] |= 1 << (k%32);
pthread_mutex_unlock(&mutex_mark);
}
void initBa(int **ba, unsigned int val)
{
*ba = calloc((val/WORDSIZE)+1, sizeof(int));
}
void getPrimes(int *ba, unsigned int val)
{
int i, p;
p = -1;
for(i = 3; i<=val; i+=2){
if(!isMarked(ba, i)){
if(++p == 8){
printf(" \n");
p = 0;
}
printf("%9d", i);
}
}
printf("\n");
}
void markTwins(int *ba, unsigned int val)
{
int i;
for(i=3; i<=val; i+=2){
if(!isMarked(ba, i)){
if(isMarked(ba, i+2)){
mark(ba, i);
}
}
}
}
void *setPrimes(void *arg)
{
int *ba, thread_id, num_threads, status;
unsigned int val, i, p, start;
struct t_data *data = (struct t_data*)arg;
ba = data->ba;
thread_id = data->thread_id;
num_threads = data->num_threads;
val = data->val;
start = (2*(thread_id+2))-1; // stagger threads
i=3;
for(i=3; i<=sqrt(val); i+=2){
if(!isMarked(ba, i)){
p=start;
while(i*p <= val){
mark(ba, (i*p));
p += (2*num_threads);
}
}
}
return 0;
}
void usage(char *filename)
{
printf("Usage: \t%s [option] [arg]\n", filename);
printf("\t-q generate #'s internally only\n");
printf("\t-m [size] maximum size twin prime to calculate\n");
printf("\t-c [threads] number of threads\n");
printf("Defaults:\n\toutput results\n\tsize = INT_MAX\n\tthreads = 1\n");
}
int main(int argc, char **argv)
{
int *ba, i, num_threads, opt, output;
unsigned int val;
output = 1;
num_threads = 1;
val = INT_MAX;
while ((opt = getopt(argc, argv, "qm:c:")) != -1){
switch (opt){
case 'q': output = 0;
break;
case 'm': val = atoi(optarg);
break;
case 'c': num_threads = atoi(optarg);
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
struct t_data data[num_threads];
pthread_t thread[num_threads];
pthread_attr_t attr;
pthread_mutex_init(&mutex_mark, NULL);
initBa(&ba, val);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(i=0; i < num_threads; i++){
data[i].ba = ba;
data[i].thread_id = i;
data[i].num_threads = num_threads;
data[i].val = val;
if(0 != pthread_create(&thread[i],
&attr,
setPrimes,
(void*)&data[i])){
perror("Cannot create thread");
exit(EXIT_FAILURE);
}
}
for(i = 0; i < num_threads; i++){
pthread_join(thread[i], NULL);
}
markTwins(ba, val);
if(output)
getPrimes(ba, val);
free(ba);
return 0;
}
编辑:我摆脱了障碍,并在标记功能中添加了一个mutex_lock。输出现在很准确,但现在不止一个线程会降低它的速度。有关加快速度的建议吗?
答案 0 :(得分:1)
你的mark()
函数不是线程安全的 - 如果两个线程试图在同一个int
位置设置位,可能会用0覆盖另一个线程刚刚设置的位。
答案 1 :(得分:1)
您当前的标记实现正确,但锁定非常粗糙 - 整个阵列只有一个锁。这意味着您的线程一直在争夺该锁定。
提高性能的一种方法是使锁更精细:每个'mark'操作只需要对数组中的单个整数进行独占访问,因此每个数组条目都可以有一个互斥锁:
struct bitarray
{
int *bits;
pthread_mutex_t *locks;
};
struct t_data
{
struct bitarray ba;
unsigned int val;
int num_threads;
int thread_id;
};
void initBa(struct bitarray *ba, unsigned int val)
{
const size_t array_size = val / WORDSIZE + 1;
size_t i;
ba->bits = calloc(array_size, sizeof ba->bits[0]);
ba->locks = calloc(array_size, sizeof ba->locks[0]);
for (i = 0; i < array_size; i++)
{
pthread_mutex_init(&ba->locks[i], NULL);
}
}
void mark(struct bitarray ba, unsigned int k)
{
const unsigned int entry = k / 32;
pthread_mutex_lock(&ba.locks[entry]);
ba.bits[entry] |= 1 << (k%32);
pthread_mutex_unlock(&ba.locks[entry]);
}
请注意,您的算法具有竞争条件:考虑num_threads = 4
的示例,因此线程0从3开始,线程1从5开始,线程2从7开始。线程2可以执行完全,标记7的每个倍数,然后再次在15,之前线程0或线程1有机会将15标记为3或5的倍数。线程2将执行无用的工作,标记15的每一个倍数。
另一种选择,如果你的编译器支持Intel风格的原子内置,那就是使用那些而不是锁:
void mark(int *ba, unsigned int k)
{
__sync_or_and_fetch(&ba[k/32], 1U << k % 32);
}