Summary of C + + multithreading programming

Source: Internet
Author: User
Tags control characters
In the development of C + + programs, generally in the throughput, concurrency, real-time high requirements. When designing a C + + program, the following points can be summed up to improve efficiency:

    • Concurrent

    • Asynchronous

    • Cache

The following will be my usual work encountered some problems twos, the design of the idea is nothing more than three points.

1 Task Queue

1.1 Designing task queues with producer-consumer models

The producer-consumer model is a very familiar model, such as in a server program, when the user data is modified by the logical module, it generates a task of updating the database (produce), to the IO Module task queue, The IO module pulls the task execution SQL operation (consume) from the task queue.

To design a common task queue, the sample code is as follows:

Detailed implementations can be found in:

Http://ffown.googlecode.com/svn/trunk/fflib/include/detail/task_queue_impl.h

void task_queue_t::p roduce (const task_t& task_) {lock_guard_t lock (M_mutex), if (M_tasklist->empty ()) {//! The condition satisfies the wake-up Wait thread m_cond.signal ();} M_tasklist->push_back (task_);} int Task_queue_t::comsume (task_t& task_) {lock_guard_t lock (M_mutex); while (M_tasklist->empty ())//! when there is no job, Wait until the condition is met wake {if (false = = M_flag) {return-1;} M_cond.wait ();} Task_ = M_tasklist->front (); M_tasklist->pop_front (); return 0;}


1.2 Task Queue Usage tips

1.2.1 IO and logic separation

For example, in a network game server program, the network module receives a message packet, which is returned immediately after it is posted to the logical layer, continuing to accept the next message packet. Logical threads operate in an environment that does not have an IO operation to guarantee real-time performance. Example:

void Handle_xx_msg (long uid, const xx_msg_t& msg) {logic_task_queue->post (Boost::bind (&servie_t::p roces, UID, msg));}


Note that this mode is a single-task queue, with each task queue single-threaded.

1.2.2 Parallel pipelining

The above only completes the parallel of IO and CPU operation, while the logical operation in CPU is serial. In some cases, the CPU logic operation part can also be implemented in parallel, such as in-game User a vegetables and b vegetables two operations can be completely parallel, because two operations do not share data. The simplest way is that A and B related operations are assigned to different task queues. Examples are as follows:

void Handle_xx_msg (long uid, const xx_msg_t& msg) {logic_task_queue_array[uid% sizeof (Logic_task_queue_array)]- >post (Boost::bind (&servie_t::p roces, UID, msg));}


Note that this mode is a multi-task queue, with one single thread per task queue.


1.2.3 Connection pooling and asynchronous callbacks

For example, the logic service module needs the database module to load the user data asynchronously, and do the subsequent processing calculation. While the database module has a connection pool with a fixed number of connections, when the task of executing SQL arrives, select an idle connection, execute the SQL, and pass SQL through the callback function to the logical layer. The steps are as follows:

Pre-allocating thread pools, each creating a connection to the database

Create a task queue for the database module, and all threads are consumers of this task queue

The logical layer wants the database module to post SQL to perform tasks while passing a callback function to accept the results of SQL execution

Examples are as follows:

void Db_t:load (Long uid_, Boost::functionpost (Boost::bind (&db_t:load, UID, func));

Note that this mode is a single-task queue, with each task queue multithreaded.


2. Log

This article mainly talk about C + + multithreaded programming, the log system is not to improve the efficiency of the program, but in the program debugging, run the wrong time, the log is an irreplaceable tool, I believe that the development of background program friends will use the log. There are several common ways to use logs:


Streaming, such as LogStream << "start Servie time[%d]" << time (0) << app name[%s] << app_string.c_str () <& Lt Endl

Printf format such as: Logtrace (Log_module, "Start Servie time[%d] app name[%s]", Time (0), app_string.c_str ());


Both have advantages and disadvantages, streaming is thread-safe, printf format format string will be more direct, but the disadvantage is that thread is unsafe, if the APP_STRING.C_STR () replaced by app_string (std::string), the compilation is passed, But the run time will be crash (if the luck is good every time crash, bad luck occasionally will crash). I personally love the printf style and can do the following improvements:


Thread safety can be achieved by increasing thread safety and using the traits mechanism of C + + templates. Example:

Templatevoid logtrace (const char* module, const char* FMT, ARG1 arg1) {Boost::format s (FMT); F% arg1;}

This way, except for standard type +std::string incoming other types will compile without passing. Here is an example of a parameter that can be overloaded to support more parameters, and can support 9 parameters or more if you wish.

Add color to the log, add control characters to printf, and then display the color on the screen terminal, linux example: printf ("33[32;49;1m [done] 33[39;49;0m")

For more color schemes see:

Http://hi.baidu.com/jiemnij/blog/item/d95df8c28ac2815cb219a80e.html

When each thread starts, it should use the log to print what function the thread is responsible for. This way, when the program runs up through the top–h–p PID can tell how much the function uses the CPU. In fact, every line of my log prints the thread ID, which is not pthread_id, but is actually the process ID number of the system assigned by the thread.


3. Performance monitoring

Although there are many tools that can analyze the performance of C + + programs, most of them run in the program debug phase. We need a means to monitor the program in both the debug and release phases, on the one hand, the bottleneck of the program, and the early detection of which components in the run-time anomalies.


It is common to use gettimeofday to calculate a function cost that can be precise to subtle. Can take advantage of the deterministic destructor of C + +, a very convenient implementation of the function to get the cost of the gadget, examples are as follows:

struct Profiler{profiler (const char* func_name) {gettimeofday (&TV, NULL);} ~profiler () {struct Timeval tv2;gettimeofday (&TV2, NULL); Long cost = (tv.tv_sec-tv.tv_sec) * 1000000 + (TV.TV_USEC- TV.TV_USEC);//! Post to some manager}struct timeval TV;}; #define Profiler () Profiler (__function__)

Cost should be posted to the performance statistics manager, which regularly outputs the performance statistics to a file.

4 LAMBDA Programming

Using foreach instead of iterators

Many programming languages have built-in foreach, but C + + has not. It is recommended that you write the foreach function where you need to traverse the container. People who are accustomed to functional programming should be very fond of using foreach, and some of the benefits of using foreach are some, such as:

Http://www.cnblogs.com/chsword/archive/2007/09/28/910011.html

But mainly in the aspect of programming philosophy.

Example:

void User_mgr_t::foreach (Boost::function func_) {for (Iterator it = M_users.begin (); It! = M_users.end () ++it) {func_ (it-& Gt;second);}}


For example, to implement the dump interface, you do not need to rewrite the code about the iterator

void User_mgr_t:dump () {struct lambda {static void print (user_t& user) {//! print (ToString (user);}}; This->foreach (lambda::p rint);}

In fact, the above code is a workaround to generate anonymous functions, if it is the C + + 11 standard compiler, can write more concise:

This->foreach ([] (user_t& user) {});

But most of the time I've written a program that runs on CentOS, you know, its GCC version is GCC 4.1.2, so most of the time I use lambda functions in a flexible way.


LAMBDA functions with task queues for asynchronous


The common code to implement async using a task queue is as follows:

void Service_t:async_update_user (Long uid) {task_queue->post (Boost::bind (&service_t:sync_update_user_impl, This, UID));} void Service_t:sync_update_user_impl (long uid) {user_t& user = Get_user (UID); User.update ()}


The disadvantage of this is that an interface to respond to the write two-pass function, if the parameters of a function has changed, then another parameter should be changed. And the code is not very beautiful either. Using lambda can make async look more intuitive, as if it were done immediately in an interface function. Example code:

void Service_t:async_update_user (long uid) {struct lambda {static void Update_user_impl (service_t* servie, long uid) { user_t& user = Servie->get_user (UID); User.update ();}}; Task_queue->post (Boost::bind (&lambda:update_user_impl, this, UID));}

This makes it very straightforward to modify the code directly within the interface when you want to change the interface.



5. Artifice


Implement Map/reduce with shared_ptr


The semantics of Map/reduce is that the task is divided into multiple tasks, delivered to multiple workers and executed concurrently, resulting in a result that is aggregated by reduce to produce the final result. What is the semantics of shared_ptr? When the last shared_ptr is destructor, the destructor for the managed object is called. The semantics and map/reduce processes are very similar. We just need to make our own request to divide multiple tasks. The sample procedure is as follows:


Define the request managed object, add the number of times we need to search for "oh nice" strings in 10 files, define the managed structure as follows:

struct reducer{void set_result (int index, long result) {M_result[index] = result;} ~reducer () {Long total = 0;for (int i = 0; i < sizeof (M_result), ++i) {total + = M_result[i];} //! Post total to Somewhere}long m_result[10];};



Define the worker that performs the task

void Worker_t:exe (int index_, shared_ptr ret) {ret->set_result (index, 100);}


After splitting a task, post it to a different worker

SHARED_PTR ret (new reducer ()); for (int i = 0; i <; ++i) {Task_queue[i]->post (Boost::bind (&worker_t:exe, I, R ET));}

The above is the C + + multithreaded programming summary of content, more relevant content please pay attention to topic.alibabacloud.com (www.php.cn)!

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.