Contents

std::thread_local!

Thread Local in rust

A brief summary about std::thread_local!

thread_local!

thread_local!은 rust의 std 매크로이며, thread_local 내부에 선언된 변수를 wrap한뒤 std::thread::LocalKey를 제공합니다. pub과 #[](attribute)가 허용됩니다.

std::thread::LocalKey

thread_local storage에 들어있는 값에 접근할 수 있는 key입니다.

.with()는 thread_local에 들어있는 변수에 대한 reference를 yield합니다. 이 값은 sent across threads 할 수 없고, given closure를 escape할 수 없습니다.

Drop을 통해서 escape될 때 destruct됩니다.

예시1

rust docs의 예시1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::cell::RefCell;
use std::thread;

thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

FOO.with(|f| {
    assert_eq!(*f.borrow(), 1);
    *f.borrow_mut() = 2;
});

// each thread starts out with the initial value of 1
let t = thread::spawn(move|| {
    FOO.with(|f| {
        assert_eq!(*f.borrow(), 1);
        *f.borrow_mut() = 3;
    });
});

// wait for the thread to complete and bail out on panic
t.join().unwrap();

// we retain our original value of 2 despite the child thread
FOO.with(|f| {
    assert_eq!(*f.borrow(), 2);
});
  • thread_local! 매크로를 거친 FOOLocalKey<RefCell<u32>> 타입입니다.
  • with() 매서드에 |f| {}라는 anonymous function을 넣어줍니다.
  • thread::spawn()이 없는 경우는 main thread이다.
  • main thread에서 spawn 한 쓰레드에서 borrow_mut()를 3으로 변경하더라도, main_thread의 FOO 변수값은 2 그대로이다.

예시2: icp nft

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use std::mem;

thread_local! {
    static STATE: RefCell<State> = RefCell::default();
}


#[pre_upgrade]
fn pre_upgrade() {
    let state = STATE.with(|state| mem::take(&mut *state.borrow_mut()));
    ...
}

// init or update 
#[init]
fn init(args: InitArgs) {
    STATE.with(|state| {
        let mut state = state.borrow_mut();
        state.custodians = args
            .custodians
            .unwrap_or_else(|| HashSet::from_iter([api::caller()]));
        state.name = args.name;
        state.symbol = args.symbol;
        state.logo = args.logo;
    });
}


// retrieve
#[query(name = "balanceOfDip721")]
fn balance_of(user: Principal) -> u64 {
    STATE.with(|state| {
        state
            .borrow()
            .nfts
            .iter()
            .filter(|n| n.owner == user)
            .count() as u64
    })
}
  • thread_local에 wrap된 변수를 변경하기 위해서는 RefCell에 접근하는 borrow_mut()메서드가 필요하다.
  • Read만 할 경우에는 .borrow()를 사용하면 된다.