This is explained in the documentation:
Older SQLite versions had issues with sharing connections between threads. That’s why the Python module disallows sharing connections and cursors between threads. If you still try to do so, you will get an exception at runtime.
This is actually configurable at build time, but most people don't configure and build their Python and stdlib from scratch. However, notice that the docs link to the page for pysqlite, pointing out that "sqlite3 is developed externally under the name pysqlite." And you can build and install pysqlite, and use that in place of the stdlib module, if you really need free-threading.
In fact, IIRC, as of somewhere around pysqlite 2.5/Python 3.4, sqlite3,
there's a not-quite-undocumented feature that allows you to disable the thread-safety checks by passing check_same_thread=False to the Connection constructor (and any Cursor objects you create will then inherit it from the Connection). If you do that, then you can safely share the objects between threads—but you still can't use them in parallel, only within a mutex, because concurrency hasn't been fully tested yet (which is also why it's a not-quite-undocumented feature instead of a documented feature).
The portable approach, of course, is to not share connections and cursors between threads, as the docs say.
One way to do this is to have each WorkerThread (or even each task, if they're part of a thread pool) create its own Connection. The performance cost of opening a bunch of connections isn't that high. However, make sure you read the SQLite FAQ on concurrent access and thread safety. In practice, if you want to be fully portable (including database files that may be on network shared drives, old versions of SQLite, etc.), you will want to create a shared threading.Lock and have your threads acquire that lock around each access to the database (including their initial Connection constructor).
Another way to do it is to run all your SQLite queries on a single thread, and have your other threads just submit a query and get back a future they can block on. If you can require either Python 3.2+, or the PyPI backport (which works back to 2.6, IIRC), concurrent.futures is the easiest way to do this. If not, you can build futures and a single-thread executor yourself pretty easily out of, e.g., a thread, a queue, and a condition per future.
I've personally used the futures solution a few times. It may seem like you're throwing away a lot of parallelism, but you're actually not; if you use a connection from multiple threads, it uses its own mutexes and also requires you to add mutexes on top of it; if you use multiple connections, it uses file locks, which are even heavier. If the single-thread executor isn't giving you enough concurrency, getting rid of it probably won't either, and you'll need to use MySQL or something similar instead of SQLite.