我正在尝试测试 Perl 的 ConnectionPool 概念。基本上测试是有一个共享数组来保存预先存在的 $dbh 实例,以便各个线程可以重新使用它们而不会浪费时间/每次启动连接的开销。问题是测试在非线程模式下运行良好,但在线程模式下出错。似乎 shared_clone() 方法没有成功创建池的共享副本?请看下面的测试代码。非常感谢任何见解!
use strict;
use warnings 'all';
no warnings 'uninitialized';
use threads;
use threads::shared;
use Thread::Semaphore;
use DBI ;
sub say { print @_, "\n" }
my $testDB = 'TestDB.db' ;
if ( -e $testDB ) { unlink( $testDB ); }
#Set up DB and create table with some data
my $dbName = "dbi:SQLite:dbname=$testDB" ;
my $userId = '' ;
my $password = '' ;
my $dbh = DBI->connect( $dbName, $userId, $password ) ;
$dbh->do( "create table Tbl1 ( id integer, name char(35) )" ) ;
$dbh->do( "insert into Tbl1 (id, name) values (1, 'Connection')" ) ;
$dbh->do( "insert into Tbl1 (id, name) values (2, 'Pool')" ) ;
#Testing ConnectionPool concept
my $connPool = [ $dbh, $dbh->clone(), $dbh->clone() ] ;
my $connPoolShared = shared_clone( $connPool ) ;
#Test non-threading env.
say "========= Test non-threading first ========" ;
for ( my $i=0; $i < scalar( @$connPool ); $i++ ) {
say "Is non-threading dbh member $i pingable? " . $connPool->[$i]->ping() ;
}#end for
#Test threading env.
say "\n========= Test threading next ========" ;
my @threadList ;
my $semaphore = Thread::Semaphore->new() ;
for ( my $count = 1; $count <= 3; $count++ ) {
my $t = threads->new( \&sub1 ) ;
push( @threadList, $t ) ;
}#end for
foreach ( @threadList ) {
$_->join() ;
}#end for
sub sub1 {
$semaphore->down() ;
my $threadDbh = pop( @$connPoolShared ) ;
say "Is threading dbh pingable? " . $threadDbh->ping() ;
$semaphore->up() ;
}#end sub
say "End Test" ;
好主意。但是,DBI 中的线程和线程安全部分说
如果在创建线程之前加载 DBI 和驱动程序并创建句柄,那么它将获得 DBI、驱动程序和句柄的克隆副本。
然而,句柄中的内部指针数据将引用原始解释器中的 DBI 和驱动程序。在新的解释器线程中使用这些句柄是不安全的,因此 DBI 会检测到这一点,并在使用不属于当前线程的句柄(DESTROY 除外)的任何方法调用时发出声音。
由于这个(可能是临时的)限制,新创建的线程必须自己连接到数据库。句柄不能跨线程共享。
所以看起来你运气不好,马上就来了(因为线程的工作方式)。
我确实找到了take_imp_data方法,它
让 $dbh 处于几乎死亡的僵尸状态,并从描述当前数据库连接的驱动程序返回原始实现数据的二进制字符串。它有效地将底层数据库 API 连接数据与 DBI 句柄分离。
他们说,这可以用作
你为什么要这样做?你不知道,忘了我什至提到过它。除非,也就是说,你正在实现像多线程连接池这样的高级东西。
...
返回的 $imp_data 可以作为 dbi_imp_data 属性传递给稍后的 connect() 调用,即使在同一进程的单独线程中,驱动程序也可以在其中使用它来“采用”从中获取实现数据的现有连接。
这似乎打开了一扇门,但随后它就消失了
一些事情要记住......
...
- 使用相同的 $imp_data 一次创建多个新的 $dbh 可能会导致不愉快的问题。不要那样做。
然后我不确定如何创建那个多线程池,因为获取实现数据一次使连接成为僵尸,而那个“灵魂”不能被多次使用。
由于短语“may well lead to unpleasant problems”是一个模糊且未指定的“威胁”——警告未来可能出现的微妙问题——我没有尝试。
希望创建新的连接对于您的需求和目的来说不会过于昂贵,这样您就可以那样创建您的池。