A kind of thinking about C ++ concurrent programming

Source: Internet
Author: User
Increasingly pressing challenges

Nowadays, it is too common for a single machine to have multiple independent computing units, which is particularly evident in the server processor. According to AMD's 2012-2013 server road map, the number of server processors will reach 20 in 2013. Reasonable Use of CPU resources is an issue that must be considered. Many C ++ programmers still use multi-threaded models, but controlling multithreading is not an easy task. It is prone to errors and debugging during development. Some developers discard multithreading to avoid the complexity of multithreading, while some developers invest in other languages, such as Erlang. In fact, we have other options. Theron is one of them.

What is Theron?

Theron is a C ++ Library (http://www.theron-library.com/) for concurrent programming. With Theron, we can avoid various pain points in multithreaded development, such as shared memory and thread synchronization. Theron
The actor model shows us another way of thinking.

What is an actor model?

Because of its excellent concurrency characteristics, Erlang has been concerned, and one of the key of its concurrency characteristics is that it uses the Actor Model (http://c2.com/cgi/wiki? Erlanglanguage ). And
The actor model corresponds to the object model we use in object-oriented programming. The object model promotes that everything is an object ), the actor model assumes that everything is an actor. In the actor model, the actor communicates with each other through messages, which is a significant difference from the object model. In other words, the actor model uses the message transmission mechanism to replace the Member method calls in the object model. This is significant because the message sending is non-blocking compared with the call of the member method, and it can be returned without waiting for the execution of the called method to complete. The difference is shown:

A: A () calls objb. B (). At this time, a: A () must wait until B: B () returns to continue execution. In the actor model, actor a sends a message to actor B and returns the message immediately. At this time, actor a can continue to execute, at the same time, actor B receives a message and is awakened and executed in parallel with actor.

Each actor in Theron is bound with a unique address, which can be used to send messages to the actor. Each actor has a message queue. From the encoding perspective, each actor instance creates a "Thread" (a non-system-level thread) related to the actor ). Each actor is always executed by a single thread. To sum up, the key to Theron's concurrency is that each actor executes in its own single "Thread", while multiple actors execute concurrently.

Hello Theron

Before talking about more, let's take a look at a simple example of Theron to get the most intuitive impression. In http://www.theron-library.com/You can download to the latest version of Theron, Theron
Makefile is provided for GCC compilation, and the Visual Studio solution file Theron. sln is also provided for Windows users to build Theron. It is easy to compile Theron. There are no too many obstacles. You must note that you need to specify the dependent thread library to build Theron. Theron supports three thread libraries: STD :: thread (C ++ 11 standard thread Library), boost. thread and Windows threads. When building with makefile, specify the used thread library through the threads parameter (for more details, refer to: http://www.theron-library.com/index.php? T = page & P = GCC), use
When Visual Studio is built, specify the used thread library by selecting the appropriate solution configuration (for more detailed information, see: http://www.theron-library.com/index.php? T = page & P = visual
Studio ). The following is a simple example:

# Include <stdio. h> # include <Theron/framework. h> # include <Theron/actor. h> // define a Message Type // In Theron, any type can be used as a message type. // The only constraint is that the variables of the message type can be copied. // messages can be sent by value instead of their addresses) struct stringmessage {char m_string [64] ;}; // the User-Defined actor must always inherit from Theron :: actor // The Only Way for each actor to communicate with other parts of the application is through the message class actor: Public Theron: Actor {public: inline actor () {// register the message processing function registerhandler (this, & actor: Handler);} PRIVATE: // Message Processing Function The first parameter specifies the inline void handler (const stringmessage & message, const Theron: address from) {printf ("% s \ n", message. m_string); If (! Send (message, from) printf ("failed to send message to address % d \ n", from. asinteger () ;}}; int main () {// framework object used to manage actors Theron: Framework framework; // construct an actor instance using the framework and hold its reference. // actor references are similar to those referenced in Java, C #, and other languages. // Theron :: actorref is implemented by reference counting, similar to boost: shared_ptr Theron: actorref simpleactor (framework. createactor <actor> (); // create a receiver to receive messages sent by the actor. // It is used in non-actor Code (for example For example, the main function) communicates with the actor. Theron: receiver; // constructs the message stringmessage message; strcpy (message. m_string, "Hello Theron! "); // Through the actor address, we can send a message to the actor if (! Framework. Send (message, receiver. getaddress (), simpleactor. getaddress () printf ("failed to send message! \ N "); // wait until the actor sends a message to avoid being shut down by the main thread er. Wait (); Return 0 ;}

This example is relatively simple. Hello Theron is output through actor. Note that a message will be copied when sent between actor. The actor receiving the message only references a copy of The sent message, this is intended to avoid the introduction of shared memory, synchronization, and other issues.

Theron Message Processing

As mentioned above, every actor is working on a "Thread" of its own. We can use an example to understand this. we modify the actor: Handler member method in the above example:

inline void Handler(const StringMessage& message, const Theron::Address from) {     while (true)    {        printf("%s --- %d\n", message.m_string, GetAddress().AsInteger());#ifdef _MSC_VER        Sleep(1000);#else        sleep(1);#endif    }}

The handler constantly prints the message and carries the address information of the current actor. In the main function, we construct two actor instances and wake them up through messages, then observe the output result:

Hello Theron! --- 1Hello Theron! --- 2Hello Theron! --- 2Hello Theron! --- 1Hello Theron! --- 2Hello Theron! --- 1Hello Theron! --- 2Hello Theron! --- 1......

As we expected, the two actor instances work in different threads. In fact, a system-level thread will be created when the framework is created. By default, two threads will be created (the number of creation threads can be determined by the Theron: Framework constructor parameter ), they constitute a thread pool. We can determine the number of threads created based on the actual number of CPU cores to ensure that the CPU is fully utilized. How is the thread in the thread pool scheduled? For example:

The actor that receives the message will be placed in a thread-safe work queue. The actor in this queue will be taken out by the wake-up working thread and processed by the message. There are two points to note in this process:

  1. For an actor, we can register multiple message processing functions for a message type. Then, multiple message processing functions corresponding to this message type will be executed in serial mode in the registration order.
  2. The thread processes the messages received by the actor in sequence. If one message is not processed completely and the next message in the message queue is not processed, we can imagine that if there are three actors, two actor message processing functions have an endless loop (for example, while (true) in the preceding example). Once executed, they occupy two threads. If there are no redundant threads in the thread pool, then the other actor will be starved to death (never executed ). We can avoid the appearance of such actor in design, and of course we can also adjust the size of the thread pool to solve this problem. In Theron, the number of threads in the thread pool can be dynamically controlled, and the thread utilization can also be measured. However, too many threads will inevitably lead to excessive thread context switching overhead.
A detailed example

Let's take a look at a detailed example to learn about the convenience brought by Theron. The problem of producer and consumer is a classic thread synchronization problem. Let's take a look at how Theron solves this problem:

# Include <stdio. h> # include <Theron/framework. h> # include <Theron/actor. h> const int produce_num = 5; Class Producer: Public Theron: Actor {public: inline producer (): m_item (0) {registerhandler (this, & Producer :: produce);} PRIVATE: // The producer produces the inline void produce (const Int &/* message */, const Theron: address from) {int count (produce_num ); while (count --) {// simulate a production time # ifdef _ msc_ver sleep (1000); # els E sleep (1); # endif printf ("produce item % d \ n", m_item); If (! Send (m_item, from) printf ("failed to send message! \ N "); ++ m_item ;}// Number of the currently produced item: int m_item ;}; class Consumer: Public Theron: Actor {public: inline consumer (): m_consumenum (produce_num) {registerhandler (this, & Consumer: consume);} PRIVATE: inline void consume (const Int & item, const Theron: address from) {// simulate a consumption time # ifdef _ msc_ver sleep (2000); # else sleep (2); # endif printf ("consume item % d \ n", item ); -- m_consumenum; // If (m_co Nsumenum = 0) {If (! Send (0, from) printf ("failed to send message! \ N "); m_consumenum = produce_num;} int m_consumenum;}; int main () {Theron: Framework framework; Theron: actorref producer (framework. createactor <producer> (); Theron: actorref consumer (framework. createactor <consumer> (); If (! Framework. Send (0, consumer. getaddress (), producer. getaddress () printf ("failed to send message! \ N "); // use sleep here to avoid the end of the main thread. // This is only for simplicity and is not particularly reasonable. // in actual writing, we should use javaser # ifdef _ msc_ver sleep (100000); # else sleep (100); # endif return 0 ;}

Producers produce items and consumers consume items in parallel. We didn't write the code for creating threads, didn't build shared memory, and didn't process thread synchronization. All of this is done easily.

Cost and Design

Compared with traditional multi-threaded programs, Theron has many advantages. By using actor, the program can be automatically executed in parallel without developers' effort. The actor always uses messages for communication, and messages must be copied. This also means we must note that when using actor for parallel operations, we must avoid additional overhead caused by a large number of message copies.

The actor model emphasizes that everything is an actor, which can naturally be used as a guideline for us to use Theron. However, too many actors will inevitably lead to frequent communication between actors. It may be a good choice to use actor appropriately and combine the object model. For example, we can divide the system appropriately and obtain some modules with relatively independent functions. Each module is an actor, the module still uses the object model and communicates with each other through the actor message mechanism.

The future of Theron

Theron is an interesting thing. Maybe you have never used it, and you do not know the actor model. However, the idea of actor is not new, and you may even be using it. At present, I have not found the actual commercial project in which Theron is used, so there are still some unknown factors for Theron's use. Some other features, such as cross-host distributed parallel execution not supported by Theron, all of which limit the use of Theron, but the author is actively changing some things (for example, the author said that remote actors will be added in the future ). Regardless of the future of Theron, the ideas brought about by Theron and actor models will allow us to face the challenges of multiple cores more easily.

Author Profile

Liang guodong loves programming, is keen on Writing technical articles, advocates of lean ideology, and practitioners of UNIX philosophy. He has been focusing on R & D of high-performance server programs for many years and is currently responsible for R & D of online game servers.

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.