서버에서 만든 자습서를 바탕으로 Rust의 Arc, Mutex, TRAIT 경계, 라이프 사이클 경계에 대해 설명합니다.

개시하다


저자는 Rust 초보자다.잘못이 있으면 지적해 주세요.

아래의 설명에 의하면


마지막 프로젝트: 멀티스레드 웹 서버 구축

단식 빨간색 서버 구축


절차.

  • tcpListener를 127..0.1:7878
  • 로 설정
  • listener에서 고객의 요청을 처리
  • handle_connection에서 http의 제목을 읽고 내용에 따라 처리와 디스플레이 페이지를 구분합니다
  • main(설명이 없기 때문에 상술한 사이트를 보십시오)


    use std::io::prelude::*;
    use std::net::TcpListener;
    use std::net::TcpStream;
    use std::time::Duration;
    use std::fs;
    use std::thread;
    fn main() {
        // tcp接続をリッスンする。
        let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
        // listenerで来たクライアントからのリクエストをfor文で処理し、確認して文をprintする
        for stream in listener.incoming() {
            let _stream = stream.unwrap();
    
            handle_connection(_stream);
        }
    }
    
    fn handle_connection(mut stream: TcpStream) {
        // データ保持のためのbufferを用意、1024Byteとしている。0が1024個の配列。
        // 十分な大きさとしているが、本来の実装では動的にバッファーを取れるようにする必要あり
        let mut buffer = [0; 1024];
        // // TcpStreamで読み取った値をbufferに保存する
        stream.read(&mut buffer).unwrap();
    
        let get = b"GET / HTTP/1.1\r\n";
        let sleep3sec = b"GET /sleep3sec HTTP/1.1\r\n";
        let sleep2sec = b"GET /sleep2sec HTTP/1.1\r\n";
        let sleep10sec = b"GET /sleep10sec HTTP/1.1\r\n";
        let (status_line, filename) = if buffer.starts_with(get) {
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep3sec) {
            thread::sleep(Duration::from_secs(3));
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep2sec) {
            thread::sleep(Duration::from_secs(2));
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep10sec) {
            thread::sleep(Duration::from_secs(10));
            ("HTTP/1.1 200 OK", "hello.html")
        } else {
            ("HTTP/1.1 404 NOT FOUND", "404.html")
        };
        let contents = fs::read_to_string(filename).unwrap();
    
        let response = format!(
            "{}\r\nContent-Length: {}\r\n\r\n{}",
            status_line,
            contents.len(),
            contents
            );
        // byteに変換し、writeメソッドで接続に送信する
        stream.write(response.as_bytes()).unwrap();
        // flushで待機を行なって送信が終わるのを待つ
        stream.flush().unwrap();
        // bufferの値を文字列に変更する。lossyは読めない文字列を変換する
        // println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
    
    }
    

    다중 스레드 서버 구축


    절차.

  • tcpListener를 127..0.1:7878
  • 로 설정
  • 스레드를 만듭니다. 스레드는 ThreadPool 함수를 통해 Thread의 수량을 지정하여 병렬 처리를 수행할 수 있습니다
  • listener에서 고객의 요청을 처리
  • ThreadPool 함수에handle 추가요청 부분의 라인handle에 연결 연결 맡기기연결 실행 권한을 부여합니다.
  • ThreadPool에서 병행 처리를 하는 프로그램 라이브러리에서 만든sender를 사용하여receiver가 있는 루트 내worker에 요청을 보내는 처리입니다.
  • worker가 받은 요청을 처리(종료 단계에서 다음 요청을 순서대로 처리)
  • main.rs


    extern crate webserver;
    use webserver::ThreadPool;
    use std::io::prelude::*;
    use std::net::TcpListener;
    use std::net::TcpStream;
    use std::time::Duration;
    use std::fs;
    use std::thread;
    
    fn main() {
        // tcp接続をリッスンする。
        let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    
        let pool = ThreadPool::new(6);
        // listenerで来たクライアントからのリクエストをfor文で処理し、確認して文をprintする
        for stream in listener.incoming() {
            let _stream = stream.unwrap();
    
            pool.execute(|| {
                handle_connection(_stream);
            });
            // thread::spawn(|| {
            //     handle_connection(_stream);
            // });
        }   
    }
    // fn main() {
    //     // tcp接続をリッスンする。
    //     let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    //     // listenerで来たクライアントからのリクエストをfor文で処理し、確認して文をprintする
    //     for stream in listener.incoming() {
    //         let _stream = stream.unwrap();
    
    //         handle_connection(_stream);
    //     }
    // }
    
    // streamの内部状態が変化するためmutで可変とする
    fn handle_connection(mut stream: TcpStream) {
        // データ保持のためのbufferを用意、1024Byteとしている。0が1024個の配列。
        // 十分な大きさとしているが、本来の実装では動的にバッファーを取れるようにする必要あり
        let mut buffer = [0; 1024];
        // // TcpStreamで読み取った値をbufferに保存する
        stream.read(&mut buffer).unwrap();
    
        let get = b"GET / HTTP/1.1\r\n";
        let sleep3sec = b"GET /sleep3sec HTTP/1.1\r\n";
        let sleep2sec = b"GET /sleep2sec HTTP/1.1\r\n";
        let sleep10sec = b"GET /sleep10sec HTTP/1.1\r\n";
        let (status_line, filename) = if buffer.starts_with(get) {
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep3sec) {
            thread::sleep(Duration::from_secs(3));
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep2sec) {
            thread::sleep(Duration::from_secs(2));
            ("HTTP/1.1 200 OK", "hello.html")
        } else if buffer.starts_with(sleep10sec) {
            thread::sleep(Duration::from_secs(10));
            ("HTTP/1.1 200 OK", "hello.html")
        } else {
            ("HTTP/1.1 404 NOT FOUND", "404.html")
        };
        let contents = fs::read_to_string(filename).unwrap();
    
        let response = format!(
            "{}\r\nContent-Length: {}\r\n\r\n{}",
            status_line,
            contents.len(),
            contents
            );
        // byteに変換し、writeメソッドで接続に送信する
        stream.write(response.as_bytes()).unwrap();
        // flushで待機を行なって送信が終わるのを待つ
        stream.flush().unwrap();
        // bufferの値を文字列に変更する。lossyは読めない文字列を変換する
        // println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
    
    }
    

    lib.rs


    use std::sync::Arc;
    use std::sync::Mutex;
    use std::thread;
    use std::sync::mpsc;
    
    struct Worker {
        id: usize,
        thread: thread::JoinHandle<()>,
    }
    pub struct ThreadPool {
        workers: Vec<Worker>,
        sender: mpsc::Sender<Job>,
    }
    
    type Job = Box<dyn FnOnce() + Send + 'static>;
    
    impl ThreadPool {
        pub fn new(_size: usize) -> ThreadPool {
    
            assert!(_size > 0);
            // mpscで並列処理をするためのチャンネルを作成する。senderはメッセージを送る側。reciverは受け取る側になる
            let (sender, receiver) = mpsc::channel();
            // Arc<Mutex<Receiver<Job>>>とすることでreceiverに所有権の複製とその所有権をもつスレッドで更新ができることを許す。
            let receiver = Arc::new(Mutex::new(receiver));
    
            let mut workers = Vec::with_capacity(_size);
            for id in 0.._size {
                // workersに所有権の複製をしたreceiverを複数作成しpushする
                workers.push(Worker::new(id, Arc::clone(&receiver)));
            }
            println!("{:?}",workers.len());
            ThreadPool{workers,sender}
        }
        pub fn execute<F>(&self, f: F)
        where F: FnOnce() + Send + 'static,
        {
            // ヒープメモリにデータを格納する
            // (ヒープメモリでは任意のメモリ領域を確保しその先頭ポインタでアクセスするためサイズが決まってない場合に有用)
            let job = Box::new(f);
            // senderを用いてjobをreceiverにsendする
            println!("test");
            self.sender.send(job).unwrap();
        }
    
    }
    
    impl Worker {
        fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
            let thread = thread::spawn(move || {
                loop {
                    // joinHandleを使用することで現在の処理が終わるまで現在のスレッドをブロックして占有する。
                    // while letでreceiverが受け取ったジョブがなくなるまでジョブを継続的に受け取る
                    let job = receiver.lock().unwrap().recv().unwrap();
    
                    println!("Worker {} got a job; executing.", id);
    
                    job();
                }
            });
            Worker{id,thread}
        }
    }
    

    지금부터 설명 드리겠습니다.


    Arc와 Muutex에 관해서 나는 말하고 싶다. 조금도 이상하지 않다. 조금도 분명하지 않다. 조금도 분명하지 않다. 조금도 분명하지 않다. 조금도 분명하지 않다.


    다음 코드
    let receiver = Arc::new(Mutex::new(receiver));
    
    receiver는sender에서 여러 라인에서 사용하는 고객의 응답 처리를 수신합니다.
    여기에서, Arc와 Mutex 대상은 단서에서 메시지를 받는 Receiver를 잠그고 저장합니다.
    나는 왜 반드시 그렇게 해야 하는지 설명한다.

    Arc


    Rust 소유권 개념
    예를 들어 다음과 같은 경우 컴파일 오류가 발생할 수 있습니다.
    use std::thread;
    
    fn main()
    {
        let v = vec![1,2,3];
        //threadでvをプリントする
        let hdl0 = thread::spawn(move || {
            println!("{:?}", v);
        });
        //threadでvをプリントする
        let hdl1 = thread::spawn(move || {
            println!("{:?}", v);
        });
        
        hdl0.join().unwrap();
        hdl1.join().unwrap();
    }
    
    이것은 hdl0이 v의 소유권을 가지고 있기 때문에 hdl1은 이 값을 가질 수 없습니다.
    그래서 Arc를 사용합니다.
    Arc를 사용하면 소유권을 복사하고 수치를 참조할 수 있습니다.
    use std::thread;
    
    fn main(){
        let v = Arc::new(vec![1,2,3]);
        // vec![]の所有権のクローン
        let v0 = Arc::clone(&v);
        let hdl0 = thread::spawn(move || {
            println!("{:?}", v);
        });
        let hdl1 = thread::spawn(move || {
        // クローンした所有権を使用して参照を行い値を読む
            println!("{:?}", v0);
        });
        
        hdl0.join().unwrap();
        hdl1.join().unwrap();
    }
    

    Mutex


    단, 이 상태에서 v를 변경할 수 없습니다.참고 권한만 있으니까.
    이것을 변경할 수 있도록 해결된 것은 미텍스다.
    Mutex는 소유한 스레드에 대해 임시 변경 권한을 부여합니다.
    이곳의 임시적인 고려는 카라오케의 마이크를 생각하면 이해하기 쉽다.
    가라오케 마이크가 하나뿐인 상황에서 노래를 할 줄 아는 사람은 단 한 명뿐이다.그래서 마이크를 들고 있을 때만 노래를 부를 수 있다.하지만 가라오케에서 룸메이트를 하는 사람은 노래를 할 수 없는 것이 아니다.내가 돈을 지불했기 때문에 그 마이크를 들 수 있다.
    뮤텍스는 여기서 말하는 마이크뿐 아니라 사용할 수 있는 권한도 부여했다.
    마이크를 받은 사람은 노래를 부르고, 다른 사람들은 자신의 번호를 기다리며 자기 차례가 되면 마이크를 들고 노래를 부른다.
    권한을 가진 사람 사이에서 변경할 수 있는 권리를 취득함으로써 참조는 물론 실행도 가능하다.
    이렇게 하면 수신기를 사용할 수 있다.
    use std::thread;
    
    fn main()
    {
        let v = Arc::new(Mutex::new(vec![1,2,3]));
        // vec![]の所有権のクローン
        let v0 = Arc::clone(&v);
        let v1 = Arc::clone(&v);
        let hdl0 = thread::spawn(move || {
        //vを書き換えることが可能になる。(裏では一つのスレッドのみが変更できる)
    	v0.push(4);
            println!("{:?}", v);
        });
        let hdl1 = thread::spawn(move || {
    	v0.push(5);
    	// クローンした所有権を使用して参照を行い値を読む
            println!("{:?}", v0);
        });
        
        hdl0.join().unwrap();
        hdl1.join().unwrap();
    }
    

    위 두 가지는 스마트 포인터에 속한다.


    스마트 포인터는 메모리에 있는 대상을 사용할 때만 메모리를 꺼내고, 끝나면 자동으로 버려집니다.
    스마트 포인터는 포인터처럼 작동할 뿐만 아니라 부가 데이터와 능력을 갖춘 데이터 구조다.스마트 포인터의 개념은 Rust 특유의 것이 아니다.스마트 포인터는 C++를 발단으로 다른 언어에도 존재한다.
    인용은 단지 데이터를 빌려 쓰는 지침일 뿐이다.반면 지능 지침은 가리키는 데이터를 가지고 있는 경우가 많다
    Arc는 참조용 스마트 포인터, Mutex는 보유용 스마트 포인터(내 생각)

    TRAIT 경계 및 라이프 사이클 경계 정보


    다음 섹션
    pub fn execute<F>(&self, f: F)
    where F: FnOnce() + Send + 'static,
    {...
    }
    
    

    TRAIT 개념


    rust에 TRAIT라는 게 있어요.
    이것은 어떤 유형의 공통된 행위에 대한 정의다.
    예를 들어, 다음과 같은 형식의 함수를 사용할 수 있습니다.
    // 型の定義
    pub struct Tweet {
        pub username: String,
        pub content: String,
        pub reply: bool,
        pub retweet: bool,
    }
    // トレイトを指定する
    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }
    
    상기 함수에서Generics를 사용하여 임의의 종류의 매개 변수를 수신하도록 지정
    fn execute<F>(&self, f: F)
    
    이 유형은 무엇이든 가능하지만, 특정 유형의 TRAIT가 지정하는 방법을 사용하는 함수이기 때문에 TRAIT에 이러한 방법이 절대적으로 포함되어 있음을 지정하려는 경우가 있을 수 있습니다.
    이럴 때 역할을 하는 게 바로 Trait의 경지입니다.

    궤적 경계


    TRAIT 경계는 위 코드의 where 섹션입니다.
    where F: FnOnce() + Send + 'static
    
    이 코드는 excute의 매개 변수 값이 FnOnce()와 Send의 TRAIT를 설치해야 한다는 것을 나타낸다.

    라이프 사이클 경계


    그럼'static'은 뭘까요?
    이것이 바로 생명주기 경계이다
    rust는 생명주기의 개념을 가지고 있다.
    이것은 어떤 인용 변수가 언제까지 인용되는지 보여 줍니다.
    예를 들어 다음과 같은 상황에서 v의 생명주기는test 함수 내에 있다
    즉, 테스트 함수 처리가 끝난 단계에서 이 변수는 죽는다.
    따라서 변수는 기본적으로 어느 함수 내에서만 사용할 수 있다.
    fn test(){
    	let v = 0
    	// vをreturnする(vのライフサイクルはこの関数内なのでエラーになる)
    	v
    }
    
    하지만 static을 더하면 사용할 수 있어요.
    fn test() -> &'static i32{
    	let v: &'static i32 = &0;
    	// vをreturnする(staticであることを指定しているので使える)
    	v
    }
    
    fn main(){
        println!("{}",test());
    }
    
    이것은 static가 이 프로그램이 실행되는 동안 유효한 생명 주기를 지정하기 때문이다.

    static 라이프 사이클 경계


    그럼 이번에 처리한 static으로 돌아가겠습니다.
    where F: FnOnce() + Send + 'static
    
    위 코드에서 TRAIT 경계와 같은 부분에 "static"이 있습니다.
    매개변수를 전달하는 TRAIT에서 프로그램을 실행할 때 유효한 라이프 사이클만 가지도록 지정하는 참조입니다.
    예를 들어 다음과 같은 경우다음 코드는 이쪽 블로그에서 빌린 거예요.
    // 'static ライフタイム境界を満たす型Tなら何でも受け付ける
    fn i_need_static_bound_type<T: 'static>(v: T) {}
    // 'staticライフサイクルの参照だけを持つことを指定
    struct IHaveStaticRef(&'static str);
    
    fn main() {
        i_need_static_bound_type(IHaveStaticRef("abc"));
    }
    
    반대로 다음과 같은 오류
    // 'static ライフタイム境界を満たす型Tなら何でも受け付ける
    fn i_need_static_bound_type<T: 'static>(v: T) {}
    
    // 'a というライフタイムの参照だけ含む
    struct IHaveNonStaticRef<'a>(&'a str);
    
    fn main() {
        {
    	// local_stringのライフサイクルはこの{}スコープ内であるため'aはこのスコープ内のライフサイクルになる
            let local_string: String = format!("abc");
            i_need_static_bound_type(IHaveNonStaticRef(&local_string));
        }
    }
    
    즉 다음 코드의'static은 가능한 매개 변수에서 프로그래밍을 할 때 지속적이고 생명주기적인 인용만 하는 매개 변수를 지정한다.
    where F: FnOnce() + Send + 'static
    

    총결산


    다중 루틴 서버 구축의 예를 바탕으로rust의 개념을 설명했다.
    어려워하면서도 어떤 개념이든 저급한 언어로 쓰이기 때문에 내부 처리를 이해하지 못한 채 어떻게 프로그래밍을 했는지 알게 됐다.

    참고 문헌


    Rust의 두 가지'static'.
    Arc<Mutex<T>의 모양은 디자인 모드입니다
    지능 지침
    Smart pointer
    Rust용 Arc 읽기(1): Arc/Rc의 기본
    궤적
    Static

    좋은 웹페이지 즐겨찾기