【问题标题】:Perl - Synchronizing Data AccessPerl - 同步数据访问
【发布时间】:2017-05-04 20:00:22
【问题描述】:

我开始学习并行编程,我想比较一下单线程程序和多线程程序。

我需要做一个非常简单的算法,它可以在一分钟内计算出可能的质数的最大数量,并显示最后计算的质数及其在质数中的位置。

例如,素数 23,将显示为数字 23 及其位置 9,因为它是第 9 个素数。

在不使用线程的情况下,找到的素数为 233,596,最后一个素数为 3,246,107。但是通过线程,找到了 229,972 个素数,最后一个素数是 3,192,463。

我认为这是错误的,因为多线程应该比单线程获得更好的结果。我相信这是一个非常基本的错误,但我无法解决它,因为我仍然不太了解 Perl 的并行性。

这是代码。它计算一分钟单线程的素数,然后使用共享变量对四个线程进行相同的计算。

use threads;
use threads::shared;

my $seconds = 60;

 # WITHOUT THREAD #
print "\n\n Calc without Threads:\n";
my $endTime = time() + $seconds;
calcWithoutThread();


print "\n\n ----------------===========================---------------- \n";

 # WITH THREAD #
print "\n\n Calc with Threads:\n";
my $prime :shared = 5;          # Starts from the 5th prime
my $totalPrime :shared = 2; # Starts with prime 2 and prime 3
my $lastPrime :shared = 0;
my $endTime1 = time() + $seconds;
my $thread1 = threads->create(\&calcWithThread);
my $thread2 = threads->create(\&calcWithThread);
my $thread3 = threads->create(\&calcWithThread);
my $thread4 = threads->create(\&calcWithThread);
$thread1->join();
$thread2->join();
$thread3->join();
$thread4->join();
print " Was found $totalPrime prime numbers. Last prime: $lastPrime.";


# SUB's #

sub calcWithoutThread{
    $prime = 5;            # Starts from the 5th prime
    $totalPrime = 2;           # Starts with prime 2 and prime 3
    $lastPrime = 0;
    while (time() < $endTime){
        if(calcPrime($prime)){  
            $totalPrime ++;
            $lastPrime = $prime;
        }
        $prime ++;
    }
    print " Was found $totalPrime prime numbers. Last prime: $lastPrime.";
}

sub calcWithThread{
    while (time() < $endTime1) {
        lock($prime);           
        if(calcPrime($prime)){
            $totalPrime ++;
            $lastPrime = $prime;
        }
        $prime ++;
    }   
}

sub calcPrime{
    for($i=2 ; $i< sqrt ($prime) ; $i++){
        if( $prime % $i == 0){
            return 0;
        }
    }
    return 1;   
}

逻辑是线程同步进行此计算,无论是否为质数,并且在计算时也不重叠值。

【问题讨论】:

    标签: multithreading perl


    【解决方案1】:

    问题在于您的线程通过锁定变量$prime 彼此同步。这意味着它们没有机会同时运行,并且还会承受切换线程和同步访问变量的开销

    素数并不是并行处理的一个很好的测试,因为每个素数的计算取决于之前的结果。但是,您可以通过保留一个单独的 $prime 变量来做到这一点,但从四个线程的 5、6、7 和 8 开始,并在测试之间添加 4。这样他们就不会重复彼此的工作,而是一起覆盖每个整数

    这会立即带来一个问题,因为偶数中没有一个是素数,因此四个线程中的两个永远不会产生结果。它仍然是并行性的有效测试,但显然效率很低。您可以通过以 5、7、9 和 11 启动线程并在每次测试前递增 8 来解决此问题。那么每个线程都会盈利

    不要忘记你必须为单线程代码编写相同的算法,否则并行部分会获得不公平的优势


    更新

    也许更好的方法是锁定$prime 仅获取下一个要测试的数字并增加它。这样所有线程都可以并行进行计算,并且只排队等待另一个工作

    然后您必须锁定$total_prime 以防止两个线程同时递增它,并在该锁定有效时更新$last_prime。因为并行线程很可能会生成乱序的素数,所以您还必须检查刚刚找到的素数是否大于任何线程发现的最新素数

    您的子例程将如下所示。对于更改标识符,我深表歉意,但 Perl 传统上使用 snake_case,我发现 camelCase 读起来不愉快。对于很多母语不是英语,又不能这么轻易挑出大写字母的人来说,蛇纹案也容易多了

    sub calc_with_thread {
    
        while ( time() < $end_time_1 ) {
    
            my $test = do {
                lock $prime;
                $prime++;
            };
    
            if ( calc_prime($test) ) {
                lock $total_prime;
                ++$total_prime;
                $last_prime = $test if $test > $last_prime;
            }
        }   
    }
    

    【讨论】:

    • 完美!你的帖子给了我提示,并帮助我更好地理解并行性。感谢您的宝贵时间!
    【解决方案2】:

    多线程不是自动速度提升。

    共享变量是绑定的,绑定的变量很慢。

    这是一个更简单、改进的版本,它可以达到最大数量而不是时间。它避免了锁定。线程仍然慢得多,因为他们每个人都必须为他们检查的每个数字对那个缓慢的共享变量进行 get 和 set。

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Time::HiRes qw(time);
    use v5.10;
    
    use threads;
    use threads::shared;
    
    my $Max_Number = 50_000;
    
    {
        print "\n\nCalc without Threads:\n";
        my $start_time = time;
        calcWithoutThread();
        my $end_time = time;
        say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
    }
    
    my $Shared_Prime :shared = 5;
    {
        print "\n\nCalc with Threads:\n";
        my $start_time = time;
        my $thread1 = threads->create(\&calcWithThread);
        my $thread2 = threads->create(\&calcWithThread);
        my $thread3 = threads->create(\&calcWithThread);
        my $thread4 = threads->create(\&calcWithThread);
        $thread1->join();
        $thread2->join();
        $thread3->join();
        $thread4->join();
        my $end_time = time;
        say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
    }
    
    sub calcWithoutThread {
        my $prime = 5;                # Starts from the 5th prime
    
        while( $prime <= $Max_Number ) {
            calcPrime($prime++);
        }
    }
    
    sub calcWithThread {
        while( $Shared_Prime <= $Max_Number ) {
            calcPrime($Shared_Prime++);
        }
    }
    
    sub calcPrime {
        my $prime = shift;
    
        for( my $i=2 ; $i < sqrt($prime) ; $i++){
            if( $prime % $i == 0){
                return 0;
            }
        }
    
        return 1;   
    }
    

    相反,应尽可能避免共享状态或协调工作人员。例如,您可以将工作切成小块。如果你有 4 个工人,给他们每个人一个起始编号,每个人都增加 4。然后他们可以覆盖整个空间而没有共享状态。

    • 工人 1:5、9、13、...
    • 工人 2:6、10、14、...
    • 工人 3:7、11、15、...
    • 工人 4:8、12、16、...
    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Time::HiRes qw(time);
    use v5.10;
    
    use threads;
    
    my $Max_Number = 7_000;
    
    {
        print "\n\nCalc without Threads:\n";
        my $start_time = time;
        calcWithoutThread(5, 1);
        my $end_time = time;
        say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
    }
    
    {
        print "\n\nCalc with Threads:\n";
        my $start_time = time;
        my $thread1 = threads->create(\&calcWithThread, 5, 4 );
        my $thread2 = threads->create(\&calcWithThread, 6, 4 );
        my $thread3 = threads->create(\&calcWithThread, 7, 4 );
        my $thread4 = threads->create(\&calcWithThread, 8, 4 );
        $thread1->join();
        $thread2->join();
        $thread3->join();
        $thread4->join();
        my $end_time = time;
        say "$Max_Number took @{[ $end_time - $start_time ]} seconds.";
    }
    
    sub calcWithoutThread {
        my($start, $inc) = @_;
    
        my @primes;
        for( my $prime = $start; $prime <= $Max_Number; $prime += $inc ) {
            push @primes, $prime if calcPrime($prime);
        }
    
        return \@primes;
    }
    
    sub calcWithThread {
        my($start, $inc) = @_;
    
        my @primes;
        for( my $prime = $start; $prime <= $Max_Number; $prime += $inc ) {
            push @primes, $prime if calcPrime($prime);
        }
    
        return \@primes;
    }
    
    sub calcPrime {
        my $prime = shift;
    
        for( my $i=2 ; $i < sqrt($prime) ; $i++){
            if( $prime % $i == 0){
                return 0;
            }
        }
    
        return 1;   
    }
    

    对于我的 i7 MacBook(2 个真实内核,4 个虚拟),线程在 6000 左右时达到平衡。要获得结果,$thread-&gt;join 将返回来自 calcWithThread 的素数列表。

    【讨论】:

    • 所以你认为$Shared_Prime++ 是原子的?
    • @Borodin 不,没有花太多时间考虑它。出于本示例的目的,我只是想说明这不是锁定问题,而是共享变量。
    • @Schwern Ty 您的贡献!
    猜你喜欢
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多