线程安全位数组?

时间:2014-02-25 06:38:26

标签: c synchronization pthreads primes

编辑问题:是否可以通过线程安全访问位数组?我在下面的实现似乎需要互斥锁,这违背了并行化的目的。

我的任务是使用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。输出现在很准确,但现在不止一个线程会降低它的速度。有关加快速度的建议吗?

2 个答案:

答案 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); 
}