并发与线程安全

通常情况下,对象允许来自多个线程的序列化访问:一个线程可以进行调用,完成后,另一个线程再进行调用。它们不允许来自多个线程的并发访问。

与此相关的问题是,从单个线程或多个线程对多步操作进行重叠/并发执行。例如,可以从同一个 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 结果集的情况下,先后对 stmt1stmt2 调用 AdbcStatementExecuteQuery(),会发生什么?一些现有的客户端库/协议(如 libpq)不支持从单个连接并发执行查询。因此,驱动程序必须:1) 在第一次 Execute 期间将所有结果缓冲到内存中(或者以其他方式允许程序继续读取第一个结果集),2) 在第二次 Execute 时报错,或者 3) 在第二次 Execute 时使第一个语句的结果集失效。

在这种情况下,ADBC 允许驱动程序选择 1) 或 2)。如果可能且合理,驱动程序应允许并发执行,无论是因为底层协议为此设计还是通过缓冲结果集来实现。但如果无法支持,驱动程序被允许报错。

另一个用例是拥有一个单一语句,但多次执行它并并发读取结果集。例如,客户端可能希望对预编译语句(prepared statement)执行此操作。

/* 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 通常不支持以这种方式对单个预编译语句进行“重叠”使用。