質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

5001閲覧

Rustの "future cannot be sent between threads safely" エラーの原因

退会済みユーザー

退会済みユーザー

総合スコア0

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2021/09/10 20:31

Rustの "future cannot be sent between threads safely" エラーについて

RustとtokioでMutexを利用した非同期コードを書いていたところ,以下のようなエラーが出てしまいました.
Mutexがdropされていないことによるエラーだと考え明示的にdropしたのですがやはり同じエラーが出てしまいます.
このエラーの出る理由と,このエラーを回避する方法を教えていただけると嬉しいです.
初めての質問ですので至らぬ点があるかとは思いますがよろしくお願いいたします.

発生している問題・エラーメッセージ

future cannot be sent between threads safely future created by async block is not `Send` help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, i32>`rustc main.rs(1, 1): required by a bound in this main.rs(92, 22): future created by async block is not `Send` main.rs(102, 13): has type `std::sync::MutexGuard<'_, i32>` which is not `Send` main.rs(105, 19): await occurs here, with `mut a` maybe used later main.rs(112, 5): `mut a` is later dropped here spawn.rs(127, 21): required by this bound in `tokio::spawn`

エラーの再現コード

rust

1use std::sync::{Arc, Mutex}; 2 3#[tokio::test] 4async fn async_test() { 5 let handle = tokio::spawn(async move { 6 let a = Arc::new(Mutex::new(0)); 7 let ret = funcA(&a).await; 8 println!("ret: {}, a: {}", ret, a.lock().unwrap()); 9 }); 10 11 handle.await; 12} 13 14async fn funcA(this: &Arc<Mutex<i32>>) -> i32 { 15 let mut a = this.lock().unwrap(); 16 *a += 1; 17 18 let ret = funcB(this).await; 19 20 println!("funcA: {}",a); 21 22 drop(a); 23 24 ret 25} 26 27pub async fn funcB(this: &Arc<Mutex<i32>>) -> i32 { 28 let mut a = this.lock().unwrap(); 29 *a += 2; 30 31 println!("funcB: {}",a); 32 33 drop(a); 34 35 100 36}

補足情報

rustc: version 1.54.0
tokio: version 1.11.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Tokioではマルチスレッドの非同期ランタイムを使用することを前提にAPIが設計されています。そのようなランタイムで非同期タスクを実行すると、.awaitするたびに、そのタスクを実行するexecutorスレッド(OSが提供するスレッド)が切り替わる可能性があります。つまりタスクがあるスレッドから、別のスレッドに送信されることがあります。そのためtokio::spawnは非同期タスクが、Sendトレイトを実装していることを要求します。

stdのMutexlock()すると、Mutexガードと呼ばれるオブジェクトが返されます。このオブジェクトはスレッド間で安全に送信できないためSendを実装していません。問題となっているコードのfuncAでは、変数aが束縛されたMutexガードが、.awaitを超えて生存しています。このように書くとfuncAが返すFutureSendでなくなります。一方でtokio::spawnfuncAが返すFutureに対してSendを要求するためにエラーになります。

rust

1async fn funcA(this: &Arc<Mutex<i32>>) -> i32 { 2 // Mutexガードを取得し、aを束縛 3 let mut a = this.lock().unwrap(); 4 *a += 1; 5 6 // マルチスレッドの非同期ランタイムでは、ここでこのタスクを実行する 7 // スレッドが切り替わる可能性がある 8 let ret = funcB(this).await; 9 10 // Mutexガードを、.awaitを通り越して使用している 11 println!("funcA: {}",a);

Mutexガードを.awaitを超えて使いたいときにはstdMutexではなく、非同期に対応したMutexを使用します。そういうMutexは、Tokioやfuturesクレートに用意されています。

TokioのMutexを使う場合は以下のようにします。

Cargo.toml

[dependencies] # Mutexを使うためにsyncフィーチャーを追加する tokio = { version = "1.11.0", features = ["rt-multi-thread", "macros", "sync"] }

rust

1// stdのMutexを、tokioのMutexに変更する 2use std::sync::Arc; 3use tokio::sync::Mutex; 4 5#[tokio::test] 6async fn async_test() { 7 let handle = tokio::spawn(async move { 8 let a = Arc::new(Mutex::new(0)); 9 let ret = funcA(&a).await; 10 // lock().unwrap()としていたところをlock().awaitに変更する 11 println!("ret: {}, a: {}", ret, a.lock().await); 12 }); 13 14 handle.await; 15} 16 17async fn funcA(this: &Arc<Mutex<i32>>) -> i32 { 18 let mut a = this.lock().await; 19 *a += 1; 20 21 let ret = funcB(this).await; 22 23 println!("funcA: {}", a); 24 25 ret 26} 27 28pub async fn funcB(this: &Arc<Mutex<i32>>) -> i32 { 29 let mut a = this.lock().await; 30 *a += 2; 31 32 println!("funcB: {}", a); 33 34 100 35}

これでコンパイルエラーは解消しますが、実行するとデッドロックが起こり、実行が進まなくなってしまいます。理由はfuncAの冒頭でロックを取得したままfuncBをコールし、funcBの中でまたロックを取得しようとしているからです。

これを解決するためには、funcBをコールする前にロックを解除します。

rust

1async fn funcA(this: &Arc<Mutex<i32>>) -> i32 { 2 let mut a = this.lock().await; 3 *a += 1; 4 // デッドロックを避けるためにロックを解除する 5 drop(a) 6 7 // funcBをコールする 8 let ret = funcB(this).await; 9 10 // 再度ロックを取得する 11 let a = this.lock().await; 12 println!("funcA: {}", a); 13 14 ret 15}

これで完成ですが、結果的にMutexガードが.awaitを超えなくなったので、stdMutexに戻しても問題なくコンパイルできるはずです。上のようにdrop(a)を使って書くと、最初の変数aのスコープが残っているので引き続きコンパイルエラーになるのですが、以下のようにブロックを作って変数aのスコープを制限すると、コンパイルエラーが解消します。

rust

1use std::sync::{Arc, Mutex}; 2// use tokio::sync::Mutex; 3 4async fn funcA(this: &Arc<Mutex<i32>>) -> i32 { 5 // スコープを制限して、Mutexガードが.awaitを超えないようにすれば 6 // stdのMutexでも問題なくコンパイルできる 7 { 8 let mut a = this.lock().unwrap(); 9 *a += 1; 10 } 11 12 let ret = funcB(this).await; 13 14 let a = this.lock().unwrap(); 15 println!("funcA: {}", a); 16 17 ret 18}

今回のケースではstdのMutexでも動きましたが、async fnasyncブロック内では常に非同期に対応したMutexを使うほうがいいです。なぜなら、非同期に対応していないMutexはロックを取得できなくて待たされる状況になったときに、非同期タスクが切り替わらず、非同期ランタイムのexecutorスレッドが待機状態になってしまうからです。

非同期に対応しているMutexなら、ロック待ちになると非同期タスクが切り替わります。これにより非同期ランタイムのexecutorスレッドを無駄に待機状態にすることなく、効率的に使用できます。

投稿2021/09/10 23:56

tatsuya6502

総合スコア2046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/09/11 08:59

tokioにもMutexがあったのですね. おかげさまで解決しました. また,非同期処理についてもう少し勉強してみようと思います. ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問