boost创建线程
boost创建线程:
// https://segmentfault.com/u/adam1q84/articles
// https://github.com/sprinfall/boost-thread-study/tree/master/src
1、通过一个不带参数的函数创建线程。
1 #include <iostream> 2 #include <boost/thread.hpp> 3 4 void Hello() { 5 // 睡眠一秒以模拟数据处理。 6 boost::this_thread::sleep(boost::posix_time::seconds(1)); 7 8 std::cout << "Hello, World!" << std::endl; 9 } 10 11 int main() { 12 // 创建一个线程对象,注意函数 Hello 将立即运行。 13 boost::thread hello_thread(Hello); 14 15 // 等待线程结束。 16 // 否则线程还没执行(完),主程序就已经结束了。 17 hello_thread.join(); 18 19 return 0; 20 }
2、通过一个带参数的函数创建线程
#include <iostream> #include <boost/thread.hpp> void Hello(const char* what) { // 睡眠一秒以模拟数据处理。 boost::this_thread::sleep(boost::posix_time::seconds(1)); std::cout << "Hello, " << what << "!" << std::endl; } int main() { boost::thread hello_thread(Hello, "World"); // 等价于使用 bind: // boost::thread hello_thread(boost::bind(&Hello, "World")); hello_thread.join(); return 0; }
3、通过一个函数对象——即仿函数(functor)——创建线程。
1 #include <iostream> 2 #include <boost/thread.hpp> 3 4 class Hello { 5 public: 6 void operator()(const char* what) { 7 boost::this_thread::sleep(boost::posix_time::seconds(1)); 8 std::cout << "Hello, " << what << "!" << std::endl; 9 } 10 }; 11 12 int main() { 13 Hello hello; 14 15 // 方式一:拷贝函数对象。 16 boost::thread hello_thread(hello, "World"); 17 hello_thread.join(); 18 19 // 方式二:不拷贝函数对象,通过 boost::ref 传入引用。 20 // 用户必须保证被线程引用的函数对象,拥有超出线程的生命期。 21 // 比如这里通过 join 线程保证了这一点。 22 boost::thread hello_thread_ref(boost::ref(hello), "World"); 23 hello_thread_ref.join(); 24 25 return 0; 26 }
4、通过一个成员函数创建线程。
与前例不同之处在于,需要以 bind 绑定 this 指针作为第一个参数
#include <iostream> #include <boost/thread.hpp> class Hello { public: Hello() { boost::thread hello_thread(boost::bind(&Hello::Entry, this, "World")); hello_thread.join(); } private: // 线程函数 void Entry(const char* what) { boost::this_thread::sleep(boost::posix_time::seconds(1)); std::cout << "Hello, " << what << "!" << std::endl; } }; int main() { Hello hello; return 0; }
5、创建两个线程,各自倒着计数。
此例顺带演示了 detached 线程,被 detached 的线程,自生自灭,不受控制,无法再 join。
1 #include <iostream> 2 #include <boost/thread.hpp> 3 4 class Counter { 5 public: 6 Counter(int value) : value_(value) { 7 } 8 9 void operator()() { 10 while (value_ > 0) { 11 std::cout << value_ << " "; 12 --value_; 13 boost::this_thread::sleep(boost::posix_time::seconds(1)); 14 } 15 std::cout << std::endl; 16 } 17 18 private: 19 int value_; 20 }; 21 22 int main() { 23 boost::thread t1(Counter(3)); 24 t1.join(); 25 26 boost::thread t2(Counter(3)); 27 t2.detach(); 28 29 // 等待几秒,不然 t2 根本没机会执行。 30 boost::this_thread::sleep(boost::posix_time::seconds(4)); 31 32 return 0; 33 }
#include <boost/thread/thread.hpp> #include <iostream> void hello() { // 子线程处理程序 while (1) { std::cout << "Hello world,I'm child thread!" << std::endl; sleep(2); } } int main(int argc, char *argv[]) { // 定义并启动子线程 boost::thread thrd(&hello); // 主线程处理程序 while (1) { std::cout << "Hello world,I'm main thread!" << std::endl; sleep(3); } // 等待子线程结束 thrd.join(); return 0; } // 编译命令 // g++ -Wall simple_thread.cpp -o simple_thread -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <iostream> 4 5 6 // 第二种方式:复杂类型对象作为参数来创建线程 7 8 boost::mutex io_mutex; 9 10 struct count 11 { 12 count(int id) : id(id) {} 13 14 void operator()() 15 { 16 for( int i = 0; i < 10; i++ ) 17 { 18 boost::mutex::scoped_lock lock(io_mutex); 19 std::cout << id << ":" << i << std::endl; 20 } 21 } 22 23 int id; 24 }; 25 26 int main(int argc, char *argv[]) 27 { 28 boost::thread thrd1(count(1)); 29 boost::thread thrd2(count(2)); 30 31 thrd1.join(); 32 thrd2.join(); 33 34 return 0; 35 } 36 37 38 // 39 // g++ -Wall multi_thread.cpp -o multi_thread -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <iostream> 3 4 // 第三种方式:在类内部创建线程 5 // (1)类内部静态方法启动线程 6 // 在这里start()和hello()方法都必须是static方法。 7 class HelloWorld 8 { 9 public: 10 static void hello() 11 { 12 std::cout << "Hello World, I'm a child thread." << std::endl; 13 } 14 15 static void start() 16 { 17 boost::thread thread(hello); 18 thread.join(); 19 } 20 }; 21 22 int main(int argc, char *argv[]) 23 { 24 HelloWorld::start(); 25 return 0; 26 } 27 28 29 // g++ -Wall create_thread_in_class.cpp -o create_thread_in_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/bind.hpp> 3 #include <boost/function.hpp> 4 #include <iostream> 5 6 // 第三种方式:在类内部创建线程 7 // 如果要求start()和hello()方法不能是静态方法则采用下面的方法创建线程: 8 class HelloWorld 9 { 10 public: 11 void hello() 12 { 13 while (1) 14 { 15 std::cout << "Hello World, I'm child thread." << std::endl; 16 sleep(1); 17 } 18 } 19 20 void start() 21 { 22 boost::function<void()> f = boost::bind(&HelloWorld::hello, this); 23 boost::thread thread(f); 24 thread.join(); 25 } 26 }; 27 28 int main(int argc, char *argv[]) 29 { 30 HelloWorld hello; 31 hello.start(); 32 33 return 0; 34 } 35 36 37 // g++ -Wall create_thread_in_class_2.cpp -o create_thread_in_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/bind.hpp> 3 #include <iostream> 4 5 6 // 第三种方式:在类内部创建线程 7 // (3)在Singleton模式内部创建线程 8 // 9 class HelloWorld 10 { 11 public: 12 void hello() 13 { 14 while (1) 15 { 16 std::cout << "Hello World, I'm child thread" << std::endl; 17 sleep(1); 18 } 19 } 20 21 static void start() 22 { 23 boost::thread thread(boost::bind(&HelloWorld::hello, &HelloWorld::getInstance())); 24 thread.join(); 25 } 26 27 static HelloWorld& getInstance() 28 { 29 if (!instance) 30 instance = new HelloWorld; 31 return *instance; 32 } 33 34 private: 35 HelloWorld(){}; 36 static HelloWorld* instance; 37 }; 38 39 40 HelloWorld* HelloWorld::instance = 0; 41 42 int main(int argc, char* argv[]) 43 { 44 HelloWorld::start(); 45 return 0; 46 } 47 48 49 // g++ -Wall create_thread_singleton.cpp -o create_thread_in_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/bind.hpp> 3 #include <boost/function.hpp> 4 #include <iostream> 5 6 class HelloWorld 7 { 8 public: 9 void hello() 10 { 11 while(1) 12 { 13 std::cout << "Hello World, I'm child thread" << std::endl; 14 sleep(1); 15 } 16 } 17 18 void start() 19 { 20 boost::function< void()> f = boost::bind(&HelloWorld::hello, this); 21 boost::thread thread(f); 22 thread.join(); 23 } 24 25 static HelloWorld& getInstance() 26 { 27 if ( !instance ) 28 { 29 instance = new HelloWorld; 30 } 31 32 return *instance; 33 } 34 35 private: 36 HelloWorld(){}; 37 static HelloWorld *instance; 38 }; 39 40 HelloWorld* HelloWorld::instance = 0; 41 42 int main(int argc, char *argv[]) 43 { 44 HelloWorld::getInstance().hello();//.start(); 45 return 0; 46 } 47 48 //g++ -Wall create_thread_singleton_1.cpp -o create_thread_in_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/bind.hpp> 3 #include <iostream> 4 5 6 // 第三种方式:在类内部创建线程 7 // (3)在Singleton模式内部创建线程 8 // 9 class HelloWorld 10 { 11 public: 12 void hello() 13 { 14 while (1) 15 { 16 std::cout << "Hello World, I'm child thread" << std::endl; 17 sleep(1); 18 } 19 } 20 21 void hello(int count) 22 { 23 while (count--) 24 { 25 std::cout << "Hello World, I'm child thread("<< count << ")" << std::endl; 26 sleep(1); 27 } 28 } 29 30 static void start() 31 { 32 //boost::thread thread(boost::bind(&HelloWorld::hello, &HelloWorld::getInstance())); 33 boost::thread thread(boost::bind(&HelloWorld::hello, &HelloWorld::getInstance(), 10)); 34 thread.join(); 35 } 36 37 static HelloWorld& getInstance() 38 { 39 if (!instance) 40 instance = new HelloWorld; 41 return *instance; 42 } 43 44 private: 45 HelloWorld(){}; 46 static HelloWorld* instance; 47 }; 48 49 50 HelloWorld* HelloWorld::instance = 0; 51 52 int main(int argc, char* argv[]) 53 { 54 HelloWorld::start(); 55 return 0; 56 } 57 58 59 // g++ -Wall create_thread_singleton.cpp -o create_thread_in_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/bind.hpp> 3 #include <boost/function.hpp> 4 #include <iostream> 5 //#include "string.h" 6 7 8 // 第四种方法:用类内部函数在类外部创建线程 9 class HelloWorld 10 { 11 public: 12 void hello(const std::string &str) 13 { 14 while(1) 15 { 16 std::cout << str << std::endl; 17 sleep(1); 18 } 19 } 20 }; 21 22 int main(int argc, char *argv[]) 23 { 24 HelloWorld helloObj; 25 boost::thread thread(boost::bind(&HelloWorld::hello, &helloObj, "Hello World, I'm child thread")); 26 thread.join(); 27 return 0; 28 } 29 30 31 32 // g++ -Wall create_thread_without_class.cpp -o create_thread_without_class -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <boost/bind.hpp> 4 #include <iostream> 5 6 //#define MUTEX_SCOPED_LOCK 1 7 //#define MUTEX_TRY_SCOPED_LOCK 1 8 #define MUTEX_SCOPED_TIMED_LOCK 1 9 10 #if MUTEX_SCOPED_LOCK 11 // mutex+scoped_lock 12 // mutex类采用Scope Lock模式实现互斥体的上锁和解锁。即构造函数对互斥体加锁,析构函数对互斥体解锁。 13 // scoped系列的特色就是析构时解锁,默认构造时加锁,这就很好的确定在某个作用域下某线程独占某段代码。 14 boost::mutex io_mutex; 15 16 void count(int id) 17 { 18 for (size_t i = 0; i < 10; i++ ) 19 { 20 boost::mutex::scoped_lock lock(io_mutex); 21 std::cout << id << ":" << i << std::endl; 22 } 23 } 24 #endif 25 26 #if MUTEX_TRY_SCOPED_LOCK 27 void loop(void) 28 { 29 bool running = true; 30 while (running) 31 { 32 static boost::try_mutex iomutex; 33 { 34 boost::try_mutex::scoped_try_lock lock(iomutex); 35 if (lock.owns_lock()) 36 { 37 std::cout << "Get Lock" << std::endl; 38 39 }else 40 { 41 // To Do 42 std::cout << "Not Get lock" << std::endl; 43 boost::thread::yield(); 44 continue; 45 } 46 } 47 48 } 49 } 50 #endif 51 52 #if MUTEX_SCOPED_TIMED_LOCK 53 void loop(void) 54 { 55 bool running = true; 56 while (running) 57 { 58 typedef boost::timed_mutex MUTEX; 59 typedef MUTEX::scoped_timed_lock LOCK; 60 61 static MUTEX iomutex; 62 { 63 boost::xtime xt; 64 boost::xtime_get(&xt, boost::TIME_UTC); 65 xt.sec += 1; // 超时时间秒 66 67 LOCK lock(iomutex, xt); 68 if (lock.owns_lock()) 69 { 70 std::cout << "Get Lock" << std::endl; 71 }else 72 { 73 std::cout << "Not Get Lock" << std::endl; 74 boost::thread::yield(); 75 } 76 } 77 } 78 } 79 #endif 80 81 int main(int argc, char *argv[]) 82 { 83 #if MUTEX_SCOPED_LOCK 84 boost::thread thread1(boost::bind(&count, 1)); 85 boost::thread thread2(boost::bind(&count, 2)); 86 #endif 87 88 #if MUTEX_TRY_SCOPED_LOCK 89 boost::thread thread1(loop); 90 boost::thread thread2(loop); 91 #endif 92 93 #if MUTEX_SCOPED_TIMED_LOCK 94 boost::thread thread1(loop); 95 boost::thread thread2(loop); 96 #endif 97 98 thread1.join(); 99 thread2.join(); 100 return 0; 101 } 102 103 104 //g++ -Wall multi_thread_mutex_with_args.cpp -o multi_thread_mutex_with_args -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/shared_mutex.hpp> 3 #include <iostream> 4 5 boost::shared_mutex shr_mutex; 6 7 static int count = 0; 8 class safe_log 9 { 10 public: 11 static void log(const std::string &log_info) 12 { 13 boost::mutex::scoped_lock lock(log_mutex); 14 std::cout << log_info << std::endl; 15 }; 16 private: 17 static boost::mutex log_mutex; 18 }; 19 20 21 boost::mutex safe_log::log_mutex; 22 23 void write_processer() 24 { 25 shr_mutex.lock(); 26 safe_log::log("begin of write process."); 27 std::cout << ++count << std::endl; 28 safe_log::log("end of write process."); 29 shr_mutex.unlock(); 30 } 31 32 void read_processer() 33 { 34 shr_mutex.lock(); 35 safe_log::log("begin of read process."); 36 std::cout << --count << std::endl; 37 safe_log::log("end of read process."); 38 shr_mutex.unlock(); 39 } 40 41 int main(int argc, char *argv[]) 42 { 43 boost::thread_group threads; 44 for (size_t i = 0; i < 1; ++i) 45 { 46 threads.create_thread(&write_processer); 47 threads.create_thread(&write_processer); 48 threads.create_thread(&read_processer); 49 } 50 51 threads.join_all(); 52 53 return 0; 54 } 55 56 // g++ -Wall multi_thread_shared_mutex.cpp -o multi_thread_shared_mutex -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <boost/thread/condition.hpp> 4 #include <iostream> 5 6 const unsigned int BUF_SIZE = 10; 7 const unsigned int ITERS = 100; 8 9 boost::mutex io_mutex; 10 11 12 // 有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。 13 // 比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情 14 // 况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。 15 class buffer 16 { 17 public: 18 typedef boost::mutex::scoped_lock scoped_lock; 19 20 buffer():p(0),c(0),full(0) 21 { 22 } 23 24 void put(int m) 25 { 26 scoped_lock lock(mutex); 27 if ( full == BUF_SIZE ) 28 { 29 { 30 boost::mutex::scoped_lock lock(io_mutex); 31 std::cout << "Buffer is full, Waiting..." << std::endl; 32 } 33 34 while ( full == BUF_SIZE ) 35 { 36 cond.wait(lock); 37 } 38 } 39 buf[p] = m; 40 p = ( p + 1 ) % BUF_SIZE; 41 ++full; 42 cond.notify_one(); 43 } 44 45 int get() 46 { 47 scoped_lock lk(mutex); 48 if ( full == 0 ) 49 { 50 { 51 boost::mutex::scoped_lock lock(io_mutex); 52 std::cout << "Buffer is empty, Waiting..." << std::endl; 53 } 54 55 while( full == 0 ) 56 { 57 cond.wait(lk); 58 } 59 } 60 int i = buf[c]; 61 c = (c + 1) % BUF_SIZE; 62 --full; 63 cond.notify_one(); 64 return i; 65 } 66 67 private: 68 boost::mutex mutex; 69 boost::condition cond; 70 unsigned int p, c, full; 71 int buf[BUF_SIZE]; 72 }; 73 74 75 buffer buf; 76 void writer() 77 { 78 for (size_t n = 0; n < ITERS; ++n) 79 { 80 { 81 boost::mutex::scoped_lock lock(io_mutex); 82 std::cout << "send:" << n << std::endl; 83 } 84 85 buf.put(n); 86 } 87 } 88 89 void reader() 90 { 91 for (size_t i = 0; i < ITERS; ++i) 92 { 93 int n = buf.get(); 94 { 95 boost::mutex::scoped_lock lock(io_mutex); 96 std::cout << "write:" << n << std::endl; 97 } 98 } 99 } 100 101 int main(int argc, char *argv[]) 102 { 103 boost::thread thread1(&reader); 104 boost::thread thread2(&writer); 105 106 thread1.join(); 107 thread2.join(); 108 109 return 0; 110 } 111 112 113 // g++ -Wall thread_condition.cpp -o thread_condition -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <boost/thread/tss.hpp> 4 #include <iostream> 5 6 7 // 线程局部存储 8 // 函数的不可重入。 9 // Boost线程库提供了智能指针boost::thread_specific_ptr来访问本地存储线程(thread local storage)。 10 boost::mutex io_mutex; 11 boost::thread_specific_ptr<int> ptr; 12 13 struct count 14 { 15 count(int id):_id(id){}; 16 void operator()() 17 { 18 if ( ptr.get() == 0 ) 19 { 20 ptr.reset(new int(0)); 21 } 22 23 for (size_t i = 0; i < 10; ++i ) 24 { 25 (*ptr)++; 26 boost::mutex::scoped_lock lock(io_mutex); 27 std::cout << _id << ":" << *ptr << std::endl; 28 } 29 } 30 31 int _id; 32 }; 33 34 int main(int argc, char *argv[]) 35 { 36 boost::thread thread1(count(1)); 37 boost::thread thread2(count(2)); 38 thread1.join(); 39 thread2.join(); 40 return 0; 41 } 42 43 // g++ -Wall thread_specific_ptr.cpp -o thread_specific_ptr -lboost_thread-mt
1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/once.hpp> 3 #include <iostream> 4 5 6 // 仅运行一次的例程 7 // 如何使得初始化工作(比如说构造函数)也是线程安全的。 8 // “一次实现”(once routine)。“一次实现”在一个应用程序只能执行一次。 9 // Boost线程库提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏 BOOST_ONCE_INIT。 10 int i = 0; 11 boost::once_flag flag = BOOST_ONCE_INIT; 12 13 void init() 14 { 15 for (size_t n = 0; n < 10; n++) 16 { 17 ++i; 18 std::cout << i << std::endl; 19 } 20 } 21 22 void thread() 23 { 24 boost::call_once(&init, flag); 25 } 26 27 int main(int argc, char *argv[]) 28 { 29 boost::thread thread1(&thread); 30 boost::thread thread2(&thread); 31 thread1.join(); 32 thread2.join(); 33 34 return 0; 35 } 36 37 38 // g++ -Wall thread_call_once.cpp -o thread_call_once -lboost_thread-mt
1 #include <boost/bind.hpp> 2 #include <boost/function.hpp> 3 #include <iostream> 4 5 using namespace boost; 6 using namespace std; 7 8 int f(int a, int b) 9 { 10 return a + b; 11 } 12 13 int g(int a, int b, int c) 14 { 15 return a + b * c; 16 } 17 18 class Point 19 { 20 public: 21 Point(int x, int y) : _x(x), _y(y) {} 22 void print(const char *msg) { 23 cout << msg << "x=" << _x << ", y=" << _y << endl; 24 } 25 private: 26 int _x, _y; 27 }; 28 29 int main() 30 { 31 //! f 有多少个参数,f 后面就要跟多少个参数。如果不明确的,用占位符 32 cout << bind(f, 2, 3)() << endl; // ==> f(2, 3) 33 cout << bind(f, 12, _1) (5) << endl; // ==> f(12, 5),其中参数b为不明确参数 34 cout << bind(g, _2, _1, 3) (3, 5) << endl; // ==> g(5, 3, 3) 注意顺序 35 36 Point p(11, 34); 37 Point &rp = p; 38 Point *pp = &p; 39 40 bind(&Point::print, p, "Point: ") (); 41 // ^ ^ 42 // 注意,为表示Point::print为成员函数,成员函数前面要加&区分。 43 // 对象可以为实例、引用、指针 44 bind(&Point::print, rp, "Reference: ") (); //! 引用 45 bind(&Point::print, pp, _1) ("Pointer: "); //! 指针 46 bind(&Point::print, _1, _2) (p, "As parameter: "); //! 对象也可以用占位符暂时代替 47 48 //! function的类型定义与需要的参数有关 49 function<void ()> func0 = bind(&Point::print, p, "func0: "); 50 function<void (const char *)> func1 = bind(&Point::print, pp, _1); 51 52 func0(); //! function对象的调用 53 func1("func1: "); 54 55 return 0; 56 } 57 58 59 // g++ -Wall bind_function_demo.cpp -o bind_function_demo -lboost_thread-mt
1 #include <boost/bind.hpp> 2 #include <boost/function.hpp> 3 #include <iostream> 4 #include <vector> 5 6 using namespace boost; 7 using namespace std; 8 9 // bind与function还可以将类型完成不同的函数(成员函数与非成员函数)包装成统一的函数调用接口。如下示例: 10 // 11 class Point 12 { 13 public: 14 Point(int x, int y) : _x(x), _y(y) {} 15 void print(string msg) { 16 cout << msg << "x=" << _x << ", y=" << _y << endl; 17 } 18 private: 19 int _x, _y; 20 }; 21 22 class Person 23 { 24 public: 25 Person(string name, int age) : _name(name), _age(age) {} 26 void sayHello() { 27 cout << "Hello, my name is " << _name << ", my age is : " << _age << endl; 28 } 29 private: 30 string _name; 31 int _age; 32 }; 33 34 void printSum(int limit) 35 { 36 int sum = 0; 37 for (int i = 0; i < limit; ++i) { 38 sum += i; 39 } 40 cout << "sum = " << sum << endl; 41 } 42 43 void doAll(vector<function<void ()> > &funcs) 44 { 45 for (size_t i = 0; i < funcs.size(); ++i) { 46 funcs[i] (); 47 } 48 } 49 50 int main() 51 { 52 vector<function<void ()> > funcList; 53 54 Person john("John", 23); 55 funcList.push_back(bind(&Person::sayHello, john)); 56 57 Point p1(23, 19); 58 funcList.push_back(bind(&Point::print, p1, "Point: ")); 59 60 funcList.push_back(bind(printSum, 20)); 61 62 doAll(funcList); 63 return 0; 64 } 65 66 67 // [root@localhost boost]# g++ -Wall bind_function_demo_1.cpp -o bind_function_demo -lboost_thread-mt 68 // [root@localhost boost]# ./bind_function_demo 69 // Hello, my name is John, my age is : 23 70 // Point: x=23, y=19 71 // sum = 190

浙公网安备 33010602011771号