并发和线程安全¶
一般来说,对象允许从多个线程进行串行访问:一个线程可以进行调用,调用完成后,另一个线程可以进行调用。它们不允许从多个线程并发访问。
与之略微相关的是从单个线程或多个线程执行多步骤操作的重叠/并发执行的问题。例如,可以从同一个 AdbcConnection 创建两个 AdbcStatement 对象。
struct AdbcStatement stmt1;
struct AdbcStatement stmt2;
struct ArrowArrayStream out1;
struct ArrowArrayStream out2;
/* Ignoring error handling for brevity */
AdbcStatementNew(&conn, &stmt1, NULL);
AdbcStatementNew(&conn, &stmt2, NULL);
AdbcStatementSetSqlQuery(&stmt1, "SELECT * FROM a", NULL);
AdbcStatementSetSqlQuery(&stmt2, "SELECT * FROM b", NULL);
AdbcStatementExecuteQuery(&stmt1, &out1, NULL, NULL);
AdbcStatementExecuteQuery(&stmt2, &out2, NULL, NULL);
/* What happens to the result set of stmt1? */
如果客户端应用程序在 stmt1
上调用 AdbcStatementExecuteQuery()
,然后在 stmt2
上调用,而没有读取 stmt1
的结果集会发生什么?一些现有的客户端库/协议,如 libpq,不支持从单个连接并发执行查询。因此,驱动程序必须 1) 在第一个 Execute
期间将所有结果缓冲到内存中(或以其他方式允许程序继续读取第一个结果集),2) 在第二个 Execute
上发出错误,或者 3) 在第二个 Execute
上使第一个语句的结果集失效。
在这种情况下,ADBC 允许驱动程序选择 1) 或 2)。如果可能且合理,驱动程序应该允许并发执行,无论是由于底层协议为此而设计还是通过缓冲结果集而实现。但如果驱动程序无法支持并发执行,则允许其报错。
另一个用例是拥有单个语句,但多次执行它并并发读取结果集。例如,客户端可能希望对准备好的语句执行此操作。
/* Ignoring error handling for brevity */
struct AdbcStatement stmt;
AdbcStatementNew(&conn, &stmt, NULL);
AdbcStatementSetSqlQuery(&stmt, "SELECT * FROM a WHERE foo > ?", NULL);
AdbcStatementPrepare(&stmt, NULL);
struct ArrowArrayStream stream;
AdbcStatementBind(&stmt, &array1, &schema, NULL);
AdbcStatementExecuteQuery(&stmt, &stream, NULL, NULL);
/* Spawn a thread to process `stream` */
struct ArrowArrayStream stream2;
AdbcStatementBind(&stmt, &array2, &schema, NULL);
AdbcStatementExecuteQuery(&stmt, &stream2, NULL, NULL);
/* What happens to `stream` here? */
ADBC 选择不允许此操作(具体来说:对 Execute
的第二次调用必须使第一次调用的结果集失效),这与现有 API 保持一致,这些 API 通常不支持以这种方式“重叠”使用单个准备好的语句。