Skip to content

Commit

Permalink
Changes to code, documentation and examples to support multi-threaded…
Browse files Browse the repository at this point in the history
… workbook generation.
  • Loading branch information
f20 committed Mar 4, 2021
1 parent 00c25b8 commit 7ca5522
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 1 deletion.
117 changes: 117 additions & 0 deletions examples/multithreaded1.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/perl

###############################################################################
#
# Example of multi-threaded workbook creation with Excel::Writer::XLSX.
# In this example, each thread waits for a process-wide lock to do its own
# clean-up after creating all the workbooks.
#
# Copyright 2000-2020, John McNamara, [email protected]
#

use strict;
use warnings;
use Excel::Writer::XLSX;
use Excel::Writer::XLSX::Utility;
use threads;
use threads::shared;

###############################################################################
#
# Declare the shared variable $cleanup_lock_counter.
#
my $cleanup_lock_counter = 0;
share $cleanup_lock_counter;

###############################################################################
#
# Generate workbooks in multiple concurrent threads.
#
my %operators = (
addition => '+',
subtraction => '-',
multiplication => '*',
division => '/',
exponentiation => '^',
);
while ( my ( $operation, $operator ) = each %operators ) {
threads->create(
make_runnable(
[ $operation, $operator, 400, 400 ],
[ $operation, $operator, 420, 420 ],
)
);
}
$_->join foreach threads->list();

###############################################################################
#
# Create the runnable for a thread to generate workbook files
# and destroy its temporary files in a controlled manner.
#
sub make_runnable {
my @instructions = @_;
sub {

{ # increment the shared variable $cleanup_lock_counter.
lock $cleanup_lock_counter;
++$cleanup_lock_counter;
}

# Make the workbooks and store the workbook objects.
my @workbookObjects = map { make_workbook(@$_); } @instructions;

{ # decrement the shared variable $cleanup_lock_counter.
lock $cleanup_lock_counter;
--$cleanup_lock_counter;
cond_signal $cleanup_lock_counter;
}

# Once $cleanup_lock_counter is 0, destroy temporary files.
lock $cleanup_lock_counter;
cond_wait $cleanup_lock_counter while $cleanup_lock_counter > 0;
$_->DESTROY foreach @workbookObjects;
cond_signal $cleanup_lock_counter;

};
}

###############################################################################
#
# Create a Excel::Writer::XLSX file.
#
sub make_workbook {

my ( $operation, $operator, $num_rows, $num_cols ) = @_;
my $long_name = "Table of $operation ($num_rows by $num_cols)";
my $workbook = Excel::Writer::XLSX->new("$long_name.xlsx") or return;
my $worksheet = $workbook->add_worksheet( ucfirst($operation) );
$worksheet->hide_gridlines(2);
my $title_format =
$workbook->add_format( size => 15, bold => 1, num_format => '@' );
my $header_format = $workbook->add_format( bg_color => 46 );
$worksheet->write( 0, 0, $long_name, $title_format );
$worksheet->write( 2, 0, $operator, $header_format );

$worksheet->write( $_ + 2, 0, $_, $header_format ) foreach 1 .. $num_rows;
$worksheet->write( 2, $_, $_, $header_format ) foreach 1 .. $num_cols;

foreach my $row ( 3 .. ( 2 + $num_rows ) ) {
foreach my $col ( 1 .. $num_cols ) {
$worksheet->write( $row, $col,
'='
. xl_rowcol_to_cell( $row, 0, 0, 1 )
. $operator
. xl_rowcol_to_cell( 2, $col, 1, 0 ) );
}
}

# Generate file.
$workbook->close();

# Return workbook object to caller, to control clean-up of temporary files.
$workbook;

}

__END__
126 changes: 126 additions & 0 deletions examples/multithreaded2.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/perl

###############################################################################
#
# Example of multi-threaded workbook creation with Excel::Writer::XLSX.
# In this example, each thread waits for a process-wide lock to do its own
# clean-up after creating each workbook.
#
# Copyright 2000-2020, John McNamara, [email protected]
#

use strict;
use warnings;
use Excel::Writer::XLSX;
use Excel::Writer::XLSX::Utility;
use threads;
use threads::shared;

###############################################################################
#
# Declare the shared variable $cleanup_lock_counter.
#
my $cleanup_lock_counter = 0;
share $cleanup_lock_counter;

###############################################################################
#
# Generate workbooks in multiple concurrent threads.
#
my %operators = (
addition => '+',
subtraction => '-',
multiplication => '*',
division => '/',
exponentiation => '^',
);
while ( my ( $operation, $operator ) = each %operators ) {
threads->create(
make_runnable(
[ $operation, $operator, 400, 400 ],
[ $operation, $operator, 420, 420 ],
)
);
}
$_->join foreach threads->list();

###############################################################################
#
# Create the runnable for a thread to generate workbook files.
#
sub make_runnable {
my @instructions = @_;
sub {
make_workbook_and_clean_up(@$_) foreach @instructions;
};
}

###############################################################################
#
# Wrapper around make_workbook to manage temporary file cleanup.
#
sub make_workbook_and_clean_up {
my ( $operation, $operator, $num_rows, $num_cols ) = @_;

{ # increment the shared variable $cleanup_lock_counter.
lock $cleanup_lock_counter;
++$cleanup_lock_counter;
}

# Make the workbook and store the workbook object.
my $workbookObject =
make_workbook( $operation, $operator, $num_rows, $num_cols );

{ # decrement the shared variable $cleanup_lock_counter.
lock $cleanup_lock_counter;
--$cleanup_lock_counter;
cond_signal $cleanup_lock_counter;
}

# Once $cleanup_lock_counter is 0, destroy temporary files.
lock $cleanup_lock_counter;
cond_wait $cleanup_lock_counter while $cleanup_lock_counter > 0;
$workbookObject->DESTROY;
cond_signal $cleanup_lock_counter;

}

###############################################################################
#
# Create a Excel::Writer::XLSX file.
#
sub make_workbook {

my ( $operation, $operator, $num_rows, $num_cols ) = @_;
my $long_name = "Table of $operation ($num_rows by $num_cols)";
my $workbook = Excel::Writer::XLSX->new("$long_name.xlsx") or return;
my $worksheet = $workbook->add_worksheet( ucfirst($operation) );
$worksheet->hide_gridlines(2);
my $title_format =
$workbook->add_format( size => 15, bold => 1, num_format => '@' );
my $header_format = $workbook->add_format( bg_color => 46 );
$worksheet->write( 0, 0, $long_name, $title_format );
$worksheet->write( 2, 0, $operator, $header_format );

$worksheet->write( $_ + 2, 0, $_, $header_format ) foreach 1 .. $num_rows;
$worksheet->write( 2, $_, $_, $header_format ) foreach 1 .. $num_cols;

foreach my $row ( 3 .. ( 2 + $num_rows ) ) {
foreach my $col ( 1 .. $num_cols ) {
$worksheet->write( $row, $col,
'='
. xl_rowcol_to_cell( $row, 0, 0, 1 )
. $operator
. xl_rowcol_to_cell( 2, $col, 1, 0 ) );
}
}

# Generate file.
$workbook->close();

# Return workbook object to caller, to control clean-up of temporary files.
$workbook;

}

__END__
111 changes: 111 additions & 0 deletions examples/multithreaded3.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/perl

###############################################################################
#
# Example of multi-threaded workbook creation with Excel::Writer::XLSX.
# In this example, each thread cleans up manually its temporary storage after
# creating each workbook. The clean-up code in this example has not been
# properly tested and relying on File::Temp's cleanup process with a lock
# as in the other multithreaded examples would be safer.
#
# Copyright 2000-2020, John McNamara, [email protected]
#

use strict;
use warnings;
use Excel::Writer::XLSX;
use Excel::Writer::XLSX::Utility;
use threads;

###############################################################################
#
# Generate workbooks in multiple concurrent threads.
#
my %operators = (
addition => '+',
subtraction => '-',
multiplication => '*',
division => '/',
exponentiation => '^',
);
while ( my ( $operation, $operator ) = each %operators ) {
threads->create(
make_runnable(
[ $operation, $operator, 400, 400 ],
[ $operation, $operator, 420, 420 ],
)
);
}
$_->join foreach threads->list();

###############################################################################
#
# Create the runnable for a thread to generate workbook files
# and destroy its temporary files in a controlled manner.
#
sub make_runnable {
my @instructions = @_;
sub {

# Make the workbooks and destroy the temporary files in a thread-safe way.
foreach (@instructions) {
my $workbook = make_workbook(@$_);
$workbook->{_tempdir_object}->unlink_on_destroy(0);
File::Find::find(
{
wanted => sub {
-f $_
? unlink $File::Find::name
: rmdir $File::Find::name;
},
no_chdir => 1,
bydepth => 1,
untaint => 1,
untaint_pattern => qr|^(.+)$|
},
$workbook->{_tempdir_object}
);
}

};
}

###############################################################################
#
# Create a Excel::Writer::XLSX file.
#
sub make_workbook {

my ( $operation, $operator, $num_rows, $num_cols ) = @_;
my $long_name = "Table of $operation ($num_rows by $num_cols)";
my $workbook = Excel::Writer::XLSX->new("$long_name.xlsx") or return;
my $worksheet = $workbook->add_worksheet( ucfirst($operation) );
$worksheet->hide_gridlines(2);
my $title_format =
$workbook->add_format( size => 15, bold => 1, num_format => '@' );
my $header_format = $workbook->add_format( bg_color => 46 );
$worksheet->write( 0, 0, $long_name, $title_format );
$worksheet->write( 2, 0, $operator, $header_format );

$worksheet->write( $_ + 2, 0, $_, $header_format ) foreach 1 .. $num_rows;
$worksheet->write( 2, $_, $_, $header_format ) foreach 1 .. $num_cols;

foreach my $row ( 3 .. ( 2 + $num_rows ) ) {
foreach my $col ( 1 .. $num_cols ) {
$worksheet->write( $row, $col,
'='
. xl_rowcol_to_cell( $row, 0, 0, 1 )
. $operator
. xl_rowcol_to_cell( 2, $col, 1, 0 ) );
}
}

# Generate file.
$workbook->close();

# Return workbook object to caller, to control clean-up of temporary files.
$workbook;

}

__END__
2 changes: 1 addition & 1 deletion lib/Excel/Writer/XLSX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ The reason for this is that Excel::Writer::XLSX relies on Perl's C<DESTROY> mech
To avoid these issues it is recommended that you always close the Excel::Writer::XLSX filehandle using C<close()>.
C<close()> is thread-safe but disposal of the Workbook object is not (because disposal of the Workbook object triggers the non-thread-safe destruction of temporary files by the C<File::Temp> module). In a program in which several threads might be concurrently writing C<Excel::Writer::XLSX> files, the Workbook objects must only be destroyed or allowed to go out of scope within a critical code section that only one thread can be running at any one time.
=head2 set_size( $width, $height )
Expand Down
Loading

0 comments on commit 7ca5522

Please sign in to comment.