Perl - 同步数据访问

时间:2017-05-04 20:00:22

标签: multithreading perl

我开始学习并行编程,我想将单线程程序与多线程程序进行比较。

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

例如,素数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;   
}

逻辑是线程同步执行此计算,如果它是素数或不是素数,并且在计算时也不重叠值。

2 个答案:

答案 0 :(得分:4)

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

对于并行处理,Primes不是一个很好的测试,因为每个素数的计算取决于之前的结果。但是,您可以通过保留单独的$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;
        }
    }   
}

答案 1 :(得分:2)

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

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

这是一个更简单,改进的版本,可以达到最大数量而不是时间。它避免了锁定。线程仍然明显变慢,因为它们中的每一个都必须为它们检查的每个数字执行get和set on slowggish共享变量。

#!/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->join将返回calcWithThread的素数列表。