레이블이 c/cpp인 게시물을 표시합니다. 모든 게시물 표시
레이블이 c/cpp인 게시물을 표시합니다. 모든 게시물 표시

2018년 11월 14일 수요일

std::promise, std::future, std::async

std::promise와 std::future를 활용해서 스레드가 진행되면서 나오는 값을 반환받을 수 있다.

c++11 에서는 async를 활용하면 더 간단하게 코드 구성이 가능하지만..

std::promise 와 std::future를 사용해 본 간단한 코드..

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
#include <iostream>
#include <cstring>
#include <future>
#include <Windows.h> // for Sleep
 
void ThreadFunc(std::promise<int>& retVal, int value)
{
    Sleep(value);
 
    retVal.set_value(value);
}
 
int main(void)
{
    // 여기서 promise 타입으로 스레드로 반환받을 형태의 template으로 선언해준다.
    std::promise<int> p1, p2;
 
    // 아래의 auto는 std::future<std::int> 이다.
    auto f1 = p1.get_future();
    auto f2 = p2.get_future();
 
    // 스레드 만들어서 호출해주고, 스레드에 인자로 위에서 선언한 것을 넣어준다.
    // std::ref(p)로 해줘도 되고, &p 해줘도 되고..
    std::thread th1(ThreadFunc, std::ref(p1), 20000);
    std::thread th2(ThreadFunc, std::ref(p2), 1000);
 
 
    // 스레드 작업이 끝날 때까지 대기함.
    // th2 작업이 먼저 끝나지만, th1.join()으로 대기하게 됨.
    // th1 작업이 끝나서 블록해제되면 그제서야 th2.join()으로 스레드 종료된거 확인하고 값 가져옴
    th1.join();
    th2.join();
 
    std::cout << f1.get() << std::endl;
    std::cout << f2.get() << std::endl;
 
    return 0;
}
cs


std::async 를 활용하면 요렇게 하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <cstring>
#include <future>
#include <Windows.h> // for Sleep
 
int ThreadFunc(int value)
{
    Sleep(value);
    return value;
}
 
int main(void)
{
    std::future<int> f1 = std::async(ThreadFunc, 20000);
    std::future<int> f2 = std::async(ThreadFunc, 1000);
    
    std::cout << f1.get() << std::endl;
    std::cout << f2.get() << std::endl;
 
    return 0;
}
cs

2018년 11월 4일 일요일

std::call_once 와 std::once_flag

std::call_once 는 아래 C++11에서 추가되었는데 아래와 같은 정의를 가지고 있다.
1
2
3
// Defined in header <mutex>
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
cs

여러 개의 스레드에서 호출될 때, 한 번만 호출되게 할 수 있다.

물론 멀티 스레드 환경이 아니더라도 아래와 같이 쓸 수도 있다.
Singleton 패턴에도 사용할 수 있겠고..

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
#include <iostream>
#include <mutex>
using namespace std;
 
class MrRobot
{
public:
    MrRobot(int serial) : serial_number_(serial) {};
 
    void PrintSerialNumber() 
    {
        std::call_once(once_flag_, [] 
        {
            cout << "MrRobot: ";
        });
 
        cout << serial_number_ << "\n";
    }
 
private:
    std::once_flag once_flag_;
    int serial_number_;
};
 
int main(void)
{
    MrRobot a(1);
    a.PrintSerialNumber();
    a.PrintSerialNumber();
 
    MrRobot b(2);
    b.PrintSerialNumber();
 
    return 0;
}
cs

위에 코드를 실행하면 결과는 아래와 같다.

MrRobot: 1
1
MrRobot: 2

디버깅 모드에서 once_flag_ 변수의 변화를 살펴보니 0x00000000 -> 0x00000002로
람다함수를 실행하면서 변경해준다.

그래서 memset 함수를 사용해서 저 변수를 다시 0으로 초기화하는 코드를 넣어보면
아래처럼 출력된다.

MrRobot: 1
MrRobot: 1
MrRobot: 2


Read Lock, Write Lock 그리고 shared_mutex

게임 서버 구현 과정에서 멀티 스레드를 사용하려다보니 특정 메모리 영역에는 동기화를 위한 작업이 필요하다.


그런데 무작정 락을 걸자니 이래저래 mutex를 남발하면 멀티 스레드가 무슨 소용인가.. 커널 왔다갔다하면서 비용이 많이 들텐데..


CRITICAL_SECTION으로 하면 유저 영역에서 해결이 되긴 한다는데...



뭐 어쨋든 락을 가능한 적게 걸기 위해, Read Lock, Write Lock 방법을 생각했는데


이 방식을 간략하게 설명하자면 아래와 같다.

"해당 영역에 변화를 주지 않는다면 여러 스레드가 접근해도 좋다"


Read Lock

 - Read하는 스레드끼리는 모두 읽어도 된다. 데이터를 변화시키지만 않는다면.

   단, 성능적인 측면을 위해 Write를 대기하는 스레드가 있다면 추가적으로 Read하는 스레드를 허용하지 않는다.


Write Lock

 - Write하는 스레드가 있을 경우, Read 스레드도 제한하고 다른 Write 스레드도 제한한다.




음.. 일단 내가 고려한 방식은 크게 2가지다. 2가지 측면을 고려하면서 성능적인 측면에서 아래와 같은 궁금증이 생겼는데


아직 테스트는 못 해봤다. 우선은 정리 먼저 해두고 테스트 코드 만들어서 해봐야지. 평일은 안 되겠고 주말에...


mutex를 사용하면 커널 영역에서 이루어지니까 2번 방법인 Interlock 함수를 이용하는 것보다 비용이 비싸지 않을까?


첫 번째는 C++11 에서 포함된 shared_mutex, unique_lock, shared_lock을 사용하는 것이다.


Read Lock을 하는 경우에는 아래처럼 사용한다.


1
shared_lock<shared_mutex> lock(mutex);
cs


그리고 Write Lock을 하는 경우에는 아래처럼 사용한다.


1
unique_lock<shared_mutex> lock(mutex);
cs


두 번째 방법으로는 Interlock 함수를 사용하는 방식이다. 
ReadWriteLock.h
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
#pragma once
#include <Windows.h>
 
class ReadWriteLock
{
public:
    ReadWriteLock();
    ~ReadWriteLock();
 
    ReadWriteLock(const ReadWriteLock& rhs) = delete;
    ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
 
    // exclusive mode
    void EnterWriteLock();
    void LeaveWriteLock();
 
    // share mode
    void EnterReadLock();
    void LeaveReadLock();
 
    long GetLockFlag() const { return lock_flag_; }
 
 
private:
    enum LockFlag
    {
        LOCK_FLAG_WRITE_MASK = 0x7FF00000,
        LOCK_FLAG_WRITE_FLAG = 0x00100000,
        LOCK_FLAG_READ_MASK  = 0x000FFFFF  // 하위 20비트를 readlock을 위한 플래그로 사용한다.
    };
 
    volatile long lock_flag_;
};
cs

ReadWriteLock.cpp
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include "pch.h"
#include "ReadWriteLock.h"
 
ReadWriteLock::ReadWriteLock()
{
}
 
ReadWriteLock::~ReadWriteLock()
{
}
 
// exclusive mode
void ReadWriteLock::EnterWriteLock()
{
    while (true)
    {
        // 다른 스레드가 write lock 풀어줄 때까지 기다린다.
        while (lock_flag_ & LOCK_FLAG_WRITE_MASK)
        {
            YieldProcessor();
        }
 
        if (LOCK_FLAG_WRITE_FLAG == (InterlockedAdd(&lock_flag_, LOCK_FLAG_WRITE_FLAG) & LOCK_FLAG_WRITE_MASK))
        {
            // 다른 스레드가 read lock 풀어줄 때까지 기다린다.
            while (lock_flag_ & LOCK_FLAG_READ_MASK)
            {
                YieldProcessor();
            }
 
            return;
        }
        InterlockedAdd(&lock_flag_, -LOCK_FLAG_WRITE_FLAG);
    }
}
 
void ReadWriteLock::LeaveWriteLock()
{
    InterlockedAdd(&lock_flag_, -LOCK_FLAG_WRITE_FLAG);
}
 
// share mode
void ReadWriteLock::EnterReadLock()
{
    while (true)
    {
        // wait for release write lock
        while (lock_flag_ & LOCK_FLAG_WRITE_MASK)
        {
            YieldProcessor();
        }
 
        // check write lock
        if ((0 == InterlockedIncrement(&lock_flag_) & LOCK_FLAG_WRITE_MASK))
        {
            return;
        }
        else
        {
            InterlockedDecrement(&lock_flag_);
        }
    }
}
 
void ReadWriteLock::LeaveReadLock()
{
    InterlockedDecrement(&lock_flag_);
}
 
cs


2017년 12월 6일 수요일

call by value & reference

C언어 입문책으로 보통 윤성우 저자분의 열혈책을 많이 사용하는데,
개발자 사이에서 잘못된 정보가 많다 vs 쉽게 설명했다 라는 의견이 있다.
둘 다 맞는 말인데.. 대표적으로 잘못된 정보가 제목에 적힌 내용이다.

c언어에서는 함수의 주소값을 넘기는 call by address 형태는 존재하지만,
reference 형태는 존재하지 않는다.

참조의 정의를 어떻게 하느냐에 따라 다를 수 있다고 볼 수도 있겠지만..
c언어의 창시자라는 데니스 리치가 쓴 the c programming language나
c11 표준에도 call by reference를 지원하지 않는다고 한다.

2016년 7월 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
 
class Robot
{
public:
    int Move(int type, int first, int second)
    {
        return (this->*m_func[type])(first, second);
    }

    int Plus(int first, int second)
    {
        return first + second;
    }

    int Minus(int first, int second)
    {
        return first - second;
    }

    static int (Robot:: * m_func[10])(int first, int second);
};
 
int (Robot::* Robot::m_func[10])(int first, int second) = {
        &Robot::Plus,
        &Robot::Minus,
};
 
int main()
{
    Robot *pRobot = new Robot;
 
    // 여기서는 Plus 함수를 호출하는 상황이 된다.
    // print --> result : 3
    int result = pRobot->Move(021);
    printf("result: %d \n", result);
    
    // 여기서는 Minus 함수를 호출하는 상황이 된다.
    // print --> result : 3
    result = pRobot->Move(121);
    printf("result: %d \n", result);
 
    return 0;
}
cs

2016년 5월 21일 토요일

C/C++ mysql connector 사용하기

예전에 만들어둔 코드..


우선 처음 사용해보시는 분들을 위해...

사용하시기 위해서는 아래 링크에서 해당 mysql connector API를 설치하시거나,

압축 파일을 받아서 푸시고 경로를 지정하시거나 하나를 선택해서 하시면 됩니다.

https://www.mysql.com/products/connector/


아래에 코드는 멀티 스레드 환경에서 동기화 작업은 고려되지 않았어요 ~_~/


pika_library_mysql_connector.h
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#ifndef PIKA_LIBRARY_MYSQL_CONNECTOR_H_
#define PIKA_LIBRARY_MYSQL_CONNECTOR_H_
// # MySqlConnector class
// DB 사용을 위한 클래스다.
// 멀티스레드 환경에서 사용에 대한 동기화는 고려되어 있지 않다.
// [NOTICE]
// - MySQL 관련 폴더를 프로젝트 속성 추가 인클루드 폴더에 설정해야된다.
// - time.h 를 사용하는 경우, timesepc 에 대해 redefinition 되었다는 에러가 뜰 수 있다.
// - mysql - connector - c - 6.1.6 - winx64 버전으로 작업 진행함.
#include <tchar.h>
#include "C:\mysql-connector-c-6.1.6-winx64\include\my_global.h"
#include "C:\mysql-connector-c-6.1.6-winx64\include\mysql.h"
#include "C:\mysql-connector-c-6.1.6-winx64\include\errmsg.h"
#pragma comment (lib, "libmysql.lib")
// 일시적으로 연결 오류 또는 끊어질 수 있는 에러들
//CR_SOCKET_CREATE_ERROR
//CR_CONNECTION_ERROR
//CR_CONN_HOST_ERROR
//CR_SERVER_GONE_ERROR
//CR_TCP_CONNECTION
//CR_SERVER_HANDSHAKE_ERR
//CR_SERVER_LOST
//CR_INVALID_CONN_HANDLE
namespace pika_library
{
    const int kQueryMaxLength = 2048;
    class MySqlConnector
    {
    public:
        explicit MySqlConnector(char *db_ip, char *user, char *password, char *db_name, int db_port);
        virtual ~MySqlConnector();
        bool        ConnectDB();
        bool        DisconnectDB();
        bool        Query(WCHAR *QueryFormat, ...);
        MYSQL_ROW   FetchRow();
        void        FreeResult();
    private:
        MYSQL       conn_;
        MYSQL       *conn_pointer_;
        MYSQL_RES   *sql_result_pointer_;
        char    db_ip_[16];
        char    db_user_[64];
        char    db_pw_[64];
        char    db_name_[64];
        int     db_port_;
        bool    connect_flag_;
        WCHAR   wchar_query_[kQueryMaxLength];
        char    char_query_[kQueryMaxLength];
        int     last_error_;
        char    last_error_msg[128];
    };
}
#endif
cs

pika_library_mysql_connector.cpp
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include "pika_library_common_header.h"
#include "Global.h"
using namespace pika_library;
MySqlConnector::MySqlConnector(char *db_ip, char *user, char *password, char *db_name, int db_port){
    mysql_init(&conn_);
    strcpy_s(db_ip_, 16, db_ip);
    strcpy_s(db_user_, 64, user);
    strcpy_s(db_pw_, 64, password);
    strcpy_s(db_name_, 64, db_name);
    db_port_ = db_port;
}
MySqlConnector::~MySqlConnector(){
}
bool        MySqlConnector::ConnectDB(){    
    if (connect_flag_ == true)
    {
        DisconnectDB();
    }
    conn_pointer_ = mysql_real_connect(&conn_, db_ip_, db_user_, db_pw_, db_name_, db_port_, NULL0);
    mysql_set_character_set(conn_pointer_, "utf8");
    if (conn_pointer_ == NULL
    {
        return false;
    }
    else
    {
        connect_flag_ = true;
        return true;
    }
}
bool        MySqlConnector::DisconnectDB(){
    mysql_close(conn_pointer_);
    connect_flag_ = false;
    return true;
}
bool        MySqlConnector::Query(WCHAR *QueryFormat, ...){
    va_list args;
    va_start(args, QueryFormat);
    _vstprintf_s(wchar_query_, kQueryMaxLength, QueryFormat, args);
    int result = WideCharToMultiByte(CP_UTF8, 0, wchar_query_, static_cast<int>(wcslen(wchar_query_)), char_query_, kQueryMaxLength, NULLNULL);
    if (result < kQueryMaxLength)
    {
        char_query_[result] = '\0';
    }
    while (TRUE) {
        int queryResult = mysql_query(conn_pointer_, char_query_);
        if (queryResult != 0)
        {
            // Query Error.
            // 현재는 화면에 출력하지만, 로그 파일에 따로 에러난 쿼리를 저장하는게 좋다.
            unsigned int error_code = mysql_errno(conn_pointer_);
            //printf("MySQL query error(%d): %s", error_code, mysql_error(conn_pointer_));
            gLogManager->Log(L"QUERY", SystemLog::kErrorLevel, L"MySql Query Error(%d) / Query: %s \n", error_code, wchar_query_);
            //CR_SOCKET_CREATE_ERROR
            //CR_CONNECTION_ERROR
            //CR_CONN_HOST_ERROR
            //CR_SERVER_GONE_ERROR
            //CR_TCP_CONNECTION
            //CR_SERVER_HANDSHAKE_ERR
            //CR_SERVER_LOST
            //CR_INVALID_CONN_HANDLE
            if (error_code == CR_SOCKET_CREATE_ERROR || error_code == CR_CONNECTION_ERROR || error_code == CR_CONN_HOST_ERROR ||
                error_code == CR_SERVER_GONE_ERROR || error_code == CR_TCP_CONNECTION || error_code == CR_SERVER_HANDSHAKE_ERR ||
                error_code == CR_SERVER_LOST || error_code == CR_INVALID_CONN_HANDLE) {
                if (ConnectDB() == false) {
                    gLogManager->Log(L"QUERY", SystemLog::kErrorLevel, L"MySql Connect False. \n");
                    Sleep(10);
                }
            }else{
                return false;
            }
        }
        else 
        {
            break;
        }
    }
    sql_result_pointer_ = mysql_store_result(conn_pointer_);
    return true;
}
MYSQL_ROW   MySqlConnector::FetchRow(){
    return mysql_fetch_row(sql_result_pointer_);
}
void        MySqlConnector::FreeResult(){
    mysql_free_result(sql_result_pointer_);
}
cs

A*, JPS 길찾기 알고리즘 시뮬레이션 사이트

https://qiao.github.io/PathFinding.js/visual/ 길 찾기 알고리즘 시행 과정을 보여주는 사이트다. 링크 메모..