Boost thread library
Bill Kempf
Translation: Logging
Standard C thread is coming. Cuj predicts that it will be derived from the Boost thread library, and now lend us to explore the Boost thread library.
Just a few years ago, a multi-threaded executive is still a non-ordinary thing. Today, today's Internet application service programs generally use multithreading to improve efficiency when linking to multi-customer links; in order to achieve maximum throughput, transaction servers run service programs on separate threads; GUI applications take those time, complex processing to thread The form is operated separately to ensure that the user interface can respond to the user's operation in time. There are still many examples of using multi-threaded.
But the C standard does not involve multithreading, which allows programmers to start suspect if the multithreaded C program may be written. Although it is impossible to write a standard multi-threaded program, programmers still use multi-threaded libraries that support multi-threaded operating systems to write multithreaded C programs. But doing this to do at least two questions: Most of these libraries are done in C language. If you want to use these libraries in the C program, you must be very careful; and every operating system has your own support. Thread library. Therefore, the code is not standard, nor the non-portable is not available everywhere. The Boost thread library is designed to solve all these problems.
BOOST is initiated by a member of the C Standard Board Working Group, which is committed to developing new libraries for C . Now it has nearly 2,000 members. Many libraries can be found in the release version of the Boost source code. In order to make these class libraries are thread-safe, the Boost thread library is created.
Many C experts are involved in the development of the Boost thread library. All interfaces are designed from 0, not a simple package of C thread API. Many C features (such as constructor and destructor, function objects, and templates) are used in it to make the interface more flexible. The current version can work in POSIX, WIN32, and Macintosh Carbon platform.
Create thread
Just like the Std :: FStream class, the Boost :: Thread class represents an executable thread. The default constructor creates an instance representing the current execution thread. An overloaded constructor is used as a parameter in a function object that does not require any parameters and has no return value. This constructor creates a new executable thread that calls that function object.
At first, everyone thinks that traditional C creation threads seem to be more useful than such designs, because C is incorporated into a void * pointer when c creation threads, and can be incorporated into data by this method. However, since the Boost thread library is used instead of a function pointer, the function object itself can carry the data required for the thread. This approach is more flexible and type-safe. When using the function library such as Boost.bind, such a method allows you to pass any number of data to the new thread.
Currently, thread object functions created by the Boost thread library are not very powerful. In fact it can only do two operations. Thread objects can be convenient to use == and! = To determine if they represent the same thread; you can also call boost :: thread :: join to wait for the thread execution. Some other thread libraries allow you to do something else thread (such as setting priority, even cancelling thread). However, since these operations to be added in the interface of the portable (portable) are not simple, it is still discussing how these journey will be added to the Boost thread library.
Listing1 shows the simplest usage of the Boost :: Thread class. The newly created thread is just simple in std :: out, "Hello, World", the main function ends after it is executed. Mutex
Anyone who writes over multi-threaded programs know the importance of avoiding different threads simultaneously accessing the shared area. If a thread wants to change a data in the shared area, while another thread is reading this data, then the result will be undefined. In order to avoid this happening, some special original types and operations are used. The most basic is the mutex (MUTEX, MUTUAL EXCLusion abbreviation). A mutex only allows a thread to access a shared area at a time. When a thread wants to access the shared area, the first thing to do is to lock (LOCK) mutex. If other threads have locked the mutex, then you must first unlock the mutex, so that only one thread can access the shared area at the same time.
The concept of mutex has many varieties. The Boost thread library supports two types of mutex, including Simple Mutex and Recursive Mutex. If the same thread has two locks on the mutex, a deadlock will occur, that is, all the threads waiting unlocked will have been waiting. With recursive muters, a single thread can be locked multiple times for mutex, of course, must unlock the same number of times to ensure that other threads can lock this mutex.
In these two categories of mutex, there are multiple variants for threads. One thread can have three ways to lock a mutex:
1. Always wait until no other thread is locked to the mutex.
2. If there is any other mutex, it has returned immediately.
3. Always wait until no other thread mutex is locked until the timeout.
It seems that the best mutex type is a recursive mutex, which can use all three upper lock forms. However, every variant is cost. So the Boost thread library allows you to use the most efficient mutex type according to different needs. The Boost thread library provides 6 mutex types, which are sorted according to efficiency:
Boost :: mutex,
Boost :: try_mutex,
Boost :: TIMED_MUTEX,
Boost :: Recursive_Mutex,
Boost :: Recursive_TRY_MUTEX,
Boost :: Recursive_timed_mutex
If you don't unlock it after the lock is locked, you will have a deadlock. This is a very common mistake, Boost thread library is to turn it into impossible (at least very difficult). It is impossible to directly lock and unlock the mutex to the user of the Boost thread library. The MuteX class defines the type of mutex and unlocks to implement the type implemented in RAII through TeypDef. This is also the Scope Lock mode you know. In order to construct these types, it is passed to a reference to a mutex. The constructor is unlocked to the mutective body, and the destructive function is unlocked. C guarantees that the destructive function will be called, so even if there is abnormal throw, the mutex will always be correctly unlocked.
This method guarantees the correct use of mutex. However, there must be a little more attention: Although the Scope Lock mode ensures that the mutex is unlocked, it does not guarantee that the resource is still available after the abnormality thrown. So just like performing a single-threaded program, it is necessary to guarantee an exception that does not cause an exception of the program. In addition, this latched object cannot be passed to another thread because the status of their maintenance does not ban it.
List2 gives the easiest example of using Boost :: Mutex. Examples have created two new threads, each thread has 10 cycles, print out the number of thread IDs and the current loop on std :: cout, and the main function waits for both thread execution finances. Std :: Cout is a shared resource, so every thread uses a global mutex to ensure that only one thread can write it. Many readers may have noticed that the data transfer data to the thread must also write a function. Although this example is very simple, if every time you have to write such a code is really annoying. Don't worry, there is a simple solution. The library allows you to create a new function by binding another function binding and incoming data required to call. List3 shows you how to use the BOOST.BIND library to simplify the code in List2, so you don't have to handle these function objects.
Condition variable
Sometimes it is not enough to rely on locking a shared resource. Sometimes the share resources can only be used when there are some states. For example, a thread is to read data from the stack, then if there is no data in the stack, it is necessary to wait for the data to be stack. Synchronous use of mutex in this case is not enough. Another synchronous manner-condition variable can be used in this case.
The use of condition variables is always associated with mutex and shared resources. The thread first locks the mutex and then verifies whether the status of the shared resource is in the state of use. If not, the thread is to wait for the condition variable. To point to such an operation, you must unlock the mutex when you wait, so that other threads can access shared resources and change their status. It also guarantees that the mutex is locked from the time of returning to the thread. When another thread changes the status of shared resources, it is necessary to notify the wait for a variable thread and return it to the wait thread.
List4 is a simple example of using Boost :: Condition. There is a class that implements a class and a fixed size, advanced, first-out container. This cache area is a thread because of the use of mutex boost :: mutex. PUT and GET use the condition variable to ensure that the thread waits to complete the status necessary. There are two threads being created, one is put into 100 integers in the buffer, and the other will take them from the buffer. This bounded cache can only store 10 integers at a time, so the two threads must wait for another thread. To verify this, PUT and GET output diagnostic statements in std :: cout. Finally, when the two threads are completed, the main function is executed.
Local storage of threads
Most functions are not reusable. This means that if you have called a function in a thread, if you call the same function, then it is not safe. An unrebeiled function saves static variables by a continuous call or returns a pointer to static data. For example, std ::stok is not renewable because it uses a static variable to save a string to be split into a symbol.
There are two ways to make unremissible functions into reusable functions. The first method is to change the interface, replace the originally used static data with a pointer or reference. For example, POSIX defines a reusable variable in Strok_r, Std :: StrtOK, which uses an additional char ** parameter instead of static data. This approach is simple and provides the best results. But this must change the public interface, which means that the code must be changed. Another approach does not have to change the public interface, but use local storage threads instead of static data (sometimes it is also a special thread storage, Thread-Specific Storage).
Boost thread library provides smart pointer boost :: thread_specific_ptr to access local storage threads. Each thread uses this smart pointer for the first time, its initial value is NULL, so you must first check this only whether it is empty and assigns it for it. Boost thread library ensures that the data saved in the local storage thread will be cleared after the thread is over. List5 is a simple example of using Boost :: Thread_Specific_PTR. It creates two threads to initialize local storage threads, and 10 cycles, each time the smart pointer points to the value, and output it on std :: coup (due to std :: cout is a shared resource, so Synchronize by a mutex). The main thread waits for the two threads to exit. From this example, you can understand that each thread handles your own data instance, although they are all using the same boost :: thread_specific_ptr.
A realization
There is also a problem without resolution: How to make initialization work (such as constructor) is also thread security. For example, if a reference program creates a unique global object, a function will be called to return a static object due to the instantiation of the order, it must ensure that this static object is generated when the first call is called. . The problem here is that if this function is called at the same time, then the constructor of this static object will be called multiple times, which is incorrect.
The method of solving this problem is the so-called "once ROUTINE". "One implementation" can only be executed once in an application. If multiple threads want to perform this operation at the same time, only one of the truly executed, while other threads must wait for this operation. In order to ensure that it is only executed once, this Routine is indirectly called by another function, and this function is transmitted to it a pointer and a special sign that marks whether this Routine has been called. This flag is initialized in a static manner, which ensures that it is initialized during compilation rather than runtime. Therefore, there is no problem that it initializes it at the same time. Boost thread library provides boost :: call_once to support "once implementation" and define a flag boost :: overce_flag and a macro boost_onit initialized this flag.
List6 is an example of using Boost :: Call_once. Among them, a static global integer is defined, the initial value is 0; there is a static boost :: ONCE_FLAG instance initialized by boost_once_init. The main function created two threads, they all want to call Boost :: Call_once by incoming a function to initialize the overall integer, which adds it 1. The main function waits for the end of the two threads and outputs the last result to std :: cout. It can be seen from the final result that this operation is indeed only executed because it is 1.
Boost thread library future
Boost thread library is planning to join some new features. These include Boost :: Read_Write_Mutex, which allows multiple threads to read data from the shared area, but only one thread is written to the shared area; Boost :: Thread_Barrier, it makes a set of threads waiting state, Knowing that all threads have entered the barrier area; Boost :: Thread_pool, he allows some small Routine to do not create or destroy a thread.
The Boost thread library has been submitted to the C Standards Committee as an attachment in the standard library technology report in the standard, and its appearance also blows the first C standard. The Committee members have given a high prior evaluation of the Boost Thread Library, of course they will consider other multi-thread libraries. They are very interested in adding support for multi-threading in C standards. It can also be seen from this point that multi-threads are bright in the future of C . Listing 1: The Boost :: Thread Class
#include
#include
Void Hello ()
{
Std :: cout <<
"Hello World, I'm A Thread!"
<< std :: endl;
}
Int main (int Argc, char * argv [])
{
Boost :: Thread thrd (& hello);
THRD.JOIN ();
Return 0;
}
- End of listing -
Listing 2: The Boost :: Mutex Class
#include
#include
#include
Boost :: Mutex IO_MUTEX;
Struct Count
{
COUNT (INT): ID (ID) {}
Void Operator () ()
{
For (int i = 0; i <10; i)
{
Boost :: Mutex :: Scoped_lock
LOCK (IO_MUTEX);
Std :: cout << id << ":"
<< i << std :: endl;
}
}
Int ID;
}
Int main (int Argc, char * argv [])
{
Boost :: Thread thrd1 (count (1));
Boost :: Thread Thrd2 (COUNT (2));
THRD1.JOIN ();
THRD2.JOIN ();
Return 0;
}
- End of listing -
Listing 3: Using the boost.bind library to simplify the code in listing 2
// this program is identical to
// listing2.cpp Except That IT
// Uses boost.bind to simplify
// the creation of a thread there
// Takes Data.
#include
#include
#include
#include
Boost :: Mutex IO_MUTEX;
Void Count (INT ID)
{
For (int i = 0; i <10; i)
{
Boost :: Mutex :: Scoped_lock
LOCK (IO_MUTEX);
Std :: cout << ID << ":" <<
i << std :: endl;
}
}
Int main (int Argc, char * argv [])
{
Boost :: Thread Thrd1 (Boost :: Bind, 1));
Boost :: Thread thrd2 (
Boost :: Bind (& Count, 2));
THRD1.JOIN ();
THRD2.JOIN ();
Return 0;
}
- End of listing -
Listing 4: The Boost :: Condition Class
#include
#include
#include
#include
Const int buf_size = 10;
Const int iters = 100;
Boost :: Mutex IO_MUTEX;
Class buffer
{
PUBLIC:
Typedef Boost :: Mutex :: Scoped_lock scoped_lock;
Buffer ()
: p (0), c (0), FULL (0)
{
}
Void Put (Int M)
{
Scoped_lock lock;
IF (full == buf_size)
{
{
Boost :: Mutex :: Scoped_lock Lock (IO_MUTEX);
Std :: cout <<
"Buffer is full. Waiting ..."
<< std :: endl;
}
While (full == buf_size)
Cond.wait (LOCK);
}
BUF [P] = m;
P = (p 1)% buf_size;
Full;
Cond.Notify_one ();
}
int GET ()
{
Scoped_lock LK (MUTEX);
IF (full == 0)
{
{
Boost :: Mutex :: Scoped_lock Lock (IO_MUTEX);
Std :: cout <<
"Buffer is es Empty. Waiting ..."
<< std :: endl;
}
While (fulll == 0)
Cond.wait (LK);
}
INT i = BUF [C];
C = (C 1)% buf_size;
--full;
Cond.Notify_one ();
Return I;
}
Private:
Boost :: mutex mutex;
Boost :: Condition Cond;
Unsigned Int P, C, FULL;
INT buf [buf_size];
}
Buffer buf;
void writer ()
{
For (int N = 0; n { { Boost :: Mutex :: Scoped_lock Lock (IO_MUTEX); Std :: cout << "Sending:" << n << std :: endl; } BUF.PUT (N); } } Void reader () { For (int x = 0; x { INT n = buf.get (); { Boost :: Mutex :: Scoped_lock Lock (IO_MUTEX); Std :: cout << "received:" << n << std :: endl; } } } Int main (int Argc, char * argv []) { Boost :: Thread Thrd1 (& Reader); Boost :: Thread Thrd2 (& Writer); THRD1.JOIN (); THRD2.JOIN (); Return 0; } - End of listing - Listing 5: The boost :: thread_specific_ptr class #include #include #include #include Boost :: Mutex IO_MUTEX; Boost :: thread_specific_ptr Struct Count { COUNT (INT): ID (ID) {} Void Operator () () { IF (ptr.get () == 0) Ptr.reset (new int (0)); For (int i = 0; i <10; i) { (* PTR) ; Boost :: Mutex :: Scoped_lock LOCK (IO_MUTEX); Std :: cout << id << ":" << * Ptr << std :: endl; } } Int ID; } Int main (int Argc, char * argv []) { Boost :: Thread thrd1 (count (1)); Boost :: Thread Thrd2 (COUNT (2)); THRD1.JOIN (); THRD2.JOIN (); Return 0; } - End of listing - Listing 6: a Very Simple Use of Boost :: Call_Once #include #include #include INT i = 0; Boost :: ONCE_FLAG FLAG = BOOST_ONCE_INIT; void init () { i; } Void thread () { Boost :: Call_ONCE (& Init, Flag); } Int main (int Argc, char * argv []) { Boost :: Thread Thrd1 (& thread); Boost :: Thread Thrd2 (& Thread); THRD1.JOIN (); THRD2.JOIN (); Std :: cout << i << std :: end1; Return 0; } - End of listing -