C++ multithreaded thread usage

1. Multithreading related classes

The C++11 new standard introduces five header files to support multi-threaded programming. They are<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。

<atomic>:This header mainly declares two classes, std::atomic and std::atomic_flag. It also declares a set of C-style atomic types and C-compatible atomic operation functions. This header file mainly declares the std::thread class, and the std::this_thread namespace is also in this header file. This header file mainly declares the classes related to the mutex, including the std::mutex series of classes, std::lock_guard, std::unique_lock, and other types and functions. This header file mainly declares classes related to condition variables, including std::condition_variable and std::condition_variable_any. This header file mainly declares two Provider classes, std::promise and std::package_task, and two Future classes, std::future and std::shared_future, as well as some related types and functions, std:: The async() function is declared in this header file.
<thread>:
<mutex>:
<condition_variable>:
<future>:

 

2. Thread introduction

This section will introduce std::thread the usage in detail  .

std::thread It <thread> is declared in the  header file, so the  header file std::thread must be included for  use  <thread>.

<thread> The header file declares the std::thread thread class and  std::swap (exchanges two thread objects) auxiliary functions. In addition, the namespace is   also declared in the   header file. The following is a  summary of the header files defined by the C++11 standard  :std::this_thread<thread><thread>

namespace std 
{
    #define __STDCPP_THREADS__ __cplusplus
    class thread;
    void swap(thread& x, thread& y);
    namespace this_thread 
    {
        thread::id get_id();

        void yield();

        template <class Clock, class Duration>
        void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);

        template <class Rep, class Period>
        void sleep_for(const chrono::duration<Rep, Period>& rel_time);
    }        
}

<thread> The main header file declares  std::thread class, in addition  std::this_thread declared namespace  get_id, yield, sleep_until and  sleep_for other auxiliary functions, this section will detail a little  std::thread and related functions.

 

std::thread Represents a thread object, the C++11 standard declares as follows:

namespace std 
{
    class thread 
    {
        public:
            // 类型声明:
            class id;
            typedef implementation-defined native_handle_type;

            // 构造函数、拷贝构造函数和析构函数声明:
            thread() noexcept;

            template <class F, class ...Args>
             explicit thread(F&& f, Args&&... args);

            ~thread();

            thread(const thread&) = delete;
            thread(thread&&) noexcept;
            thread& operator=(const thread&) = delete;
            thread& operator=(thread&&) noexcept;

            // 成员函数声明:
            void swap(thread&) noexcept;
            bool joinable() const noexcept;
            void join();
            void detach();
            id get_id() const noexcept;
            native_handle_type native_handle();
            
            // 静态成员函数声明:
            static unsigned hardware_concurrency() noexcept;
    };
}

std::thread Three types of functions are mainly declared in: (1). Constructor, copy constructor and destructor; (2). Member function; (3). Static member function. In addition, it  std::thread::id indicates the thread ID, and C++11 declares as follows:

namespace std 
{
    class thread::id 
    {
    public:
        id() noexcept;
    };

    bool operator==(thread::id x, thread::id y) noexcept;
    bool operator!=(thread::id x, thread::id y) noexcept;
    bool operator<(thread::id x, thread::id y) noexcept;
    bool operator<=(thread::id x, thread::id y) noexcept;
    bool operator>(thread::id x, thread::id y) noexcept;
    bool operator>=(thread::id x, thread::id y) noexcept;

    template<class charT, class traits>
    basic_ostream<charT, traits>& operator<< (basic_ostream<charT, traits>& out, thread::id id);


    // Hash 支持
    template <class T> struct hash;
    template <> struct hash<thread::id>;
}

 

std::thread Constructor

   a. The default constructor creates an empty thread execution object.
   b. Initialize the constructor, create a thread object, the thread object can be joinable, the newly generated thread will call the fn function, the parameters of the function are given by args.
   c. Copy constructor (disabled), which means that thread cannot be copy constructed.
   d. Move constructor, move constructor, after the call is successful, x does not represent any thread execution object.
Note: thread objects that can be joinable must be joined by the main thread or set to detached before they are destroyed .

 

std::thread Assignment operation

Move assignment operation (1) thread& operator=(thread&& rhs) noexcept;
Copy assignment operation [deleted] (2) thread& operator=(const thread&) = delete;

   a. Move assignment operation (1), if the current object is not available  joinable, an rvalue reference needs to be passed to the  move assignment operation; if the current object can be used  joinable, terminate() will be called to  report an error.

   b. The copy assignment operation (2) is disabled, so the   object cannot be copied and assigned .std::thread

 

join: Join thread, calling this function will block the current thread, and the *this join will not return until  the marked thread finishes executing.

At this point, you can combine the thread constructor to give an example of multithreading:

#include<iostream>
#include<thread>
#include<chrono>
using namespace std;

void f1(int n)
{
	for (int i = 0; i < 5; ++i) 
	{
		std::cout << "Thread " << n << " executing\n";
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));//1秒
	}
}

void f2(int& n)
{
	for (int i = 0; i < 5; ++i) 
	{
		std::cout << "Thread 2 executing\n";
		++n;
		std::this_thread::sleep_for(std::chrono::milliseconds(300));//0.3秒
	}
}

int main()
{
	int n = 0;
	std::thread t1; // t1 is not a thread
	std::thread t2(f1, n + 1); // pass by value,值传递
	std::thread t3(f2, std::ref(n)); // pass by reference,引用传递
	std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
	t2.join();
	t4.join();
	std::cout << "Final value of n is " << n << '\n';
	system("pause");
}

In order to show the effect of multi-threading, I deliberately delayed f1 for a cycle of one second and f2 for a cycle of 0.3 seconds. The above results are indeterminate. The output of f1 and f2 may alternate (maybe a cout has not yet ended, but the time slice is over, and it is the turn of another thread to use). The following is one of the output results. Your result may be the same as I am different:

 

detach: Detach thread. Separate the execution instance represented by the current thread object from the thread object, so that the execution of the thread can be performed separately. Once the thread has finished executing, its allocated resources will be released.

After calling the detach function:

  1. *this No longer represents any thread execution instance.
  2. joinable() == false
  3. get_id() == std::id()

In addition, if an error occurs or  joinable() == false, it will throw std::system_error

 #include <iostream>
    #include <chrono>
    #include <thread>
     
    void independentThread() 
    {
        std::cout << "Starting concurrent thread.\n";
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Exiting concurrent thread.\n";
    }
     
    void threadCaller() 
    {
        std::cout << "Starting thread caller.\n";
        std::thread t(independentThread);
        t.detach();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Exiting thread caller.\n";
    }
     
    int main() 
    {
        threadCaller();
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }

 

joinable: Check whether the thread can be joined. Check whether the current thread object represents an active thread of execution. The thread created by the default constructor cannot be joined. In addition, if a thread has completed the task but has not been joined, the thread will still be considered an active thread of execution, so it can also be joined.

  #include <iostream>
  #include <thread>
  #include <chrono>
  using namespace std;
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t;
      cout << "before starting, joinable: " << t.joinable() << endl; //值为0
   
      t = std::thread(foo);
      cout << "after starting, joinable: " << t.joinable() << endl; //值为1
   
      t.join();
      cout << "after join(), joinable: " << t.joinable() << endl; //值为0
  }

 

get_id: Get the thread ID and return an std::thread::id object of type  . Consider the following example:

  #include <iostream>
  #include <thread>
  #include <chrono>
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t1(foo);
      std::thread::id t1_id = t1.get_id();
   
      std::thread t2(foo);
      std::thread::id t2_id = t2.get_id();
   
      std::cout << "t1's id: " << t1_id << '\n';
      std::cout << "t2's id: " << t2_id << '\n';
   
      t1.join();
      t2.join();
  }

For the sub-threads branched out by the main thread, the get_id value of the sub-thread my computer gets is 76648. When the thread ends (after the join), the value returns to 0; if there is no thread (such as using the default constructor), the value is always 0.

 

swap: Swap thread, exchange the underlying handles represented by the two thread objects.

 #include <iostream>
  #include <thread>
  #include <chrono>
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  void bar()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t1(foo);
      std::thread t2(bar);
   
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      std::swap(t1, t2);
   
      std::cout << "after std::swap(t1, t2):" << std::endl;
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      t1.swap(t2);
   
      std::cout << "after t1.swap(t2):" << std::endl;
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      t1.join();
      t2.join();
  }

The results are as follows:

thread 1 id: 1892
thread 2 id: 2584
after std::swap(t1, t2):
thread 1 id: 2584
thread 2 id: 1892
after t1.swap(t2):
thread 1 id: 1892
thread 2 id: 2584

 

Yield: The current thread gives up execution, and the operating system schedules another thread to continue execution.

  #include <iostream>
  #include <chrono>
  #include <thread>
   
  // "busy sleep" while suggesting that other threads run 
  // for a small amount of time
  void little_sleep(std::chrono::microseconds us)
  {
      auto start = std::chrono::high_resolution_clock::now();
      auto end = start + us;
      do
      {
          std::this_thread::yield();
      } while (std::chrono::high_resolution_clock::now() < end);
  }
   
  int main()
  {
      auto start = std::chrono::high_resolution_clock::now();
   
      little_sleep(std::chrono::microseconds(100));
   
      auto elapsed = std::chrono::high_resolution_clock::now() - start;
      std::cout << "waited for "
                << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
                << " microseconds\n";
  }

 

sleep_until : The thread sleeps to a specified time (time point) before the thread is awakened again.

 template< class Clock, class Duration >
  void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );

 

sleep_for : The thread sleeps for a specified time span, and then the thread is awakened again. However, due to thread scheduling and other reasons, the actual sleep time may be longer than sleep_duration the indicated time span.

 template< class Rep, class Period >
  void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration );

  #include <iostream>
  #include <chrono>
  #include <thread>
   
  int main()
  {
      std::cout << "Hello waiter" << std::endl;
      std::chrono::milliseconds dura( 2000 );
      std::this_thread::sleep_for( dura );
      std::cout << "Waited 2000 ms\n";
  }

The results are as follows:

Hello waiter
Waited 2000 ms

 

native_handle: Return native handle (because  std::thread the implementation is related to the operating system, this function returns  std::thread the thread handle related to the specific implementation, for example, it is the Pthread library under the Posix standard platform (such as Unix/Linux)).

  #include <thread>
  #include <iostream>
  #include <chrono>
  #include <cstring>
  #include <pthread.h>
   
  std::mutex iomutex;
  void f(int num)
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
   
     sched_param sch;
     int policy; 
     pthread_getschedparam(pthread_self(), &policy, &sch);
     std::lock_guard<std::mutex> lk(iomutex);
     std::cout << "Thread " << num << " is executing at priority "
               << sch.sched_priority << '\n';
  }
   
  int main()
  {
      std::thread t1(f, 1), t2(f, 2);
   
      sched_param sch;
      int policy; 
      pthread_getschedparam(t1.native_handle(), &policy, &sch);
      sch.sched_priority = 20;
      if(pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch))
      {
          std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
      }
   
      t1.join();
      t2.join();
  }

The results are as follows:

Thread 2 is executing at priority 0
Thread 1 is executing at priority 20

 

hardware_concurrency [static]: Detect hardware concurrency features, and return the number of concurrent threads supported by the thread implementation of the current platform, but the return value is only used as a system hint.

  #include <iostream>
  #include <thread>
   
  int main() {
      unsigned int n = std::thread::hardware_concurrency();
      std::cout << n << " concurrent threads are supported.\n";
  }

 

3. Classic examples

(1) Multi-threaded access, to further understand the role of join : calculate the value of num, expect to get 2 0000 0000

#include<iostream>
#include<thread>
#include<mutex>

const int N = 100000000;
int num(0);
mutex m;

void run()
{
	for (int i = 0; i < N; i++)
	{
		//m.lock();  //当前线程加锁后,未释放前,其它线程不可访问下面语句
		num++;
		//m.unlock();
	}
}

int main()
{
	clock_t start = clock();

	thread t1(run);
    thread t2(run);

	t1.join();
	t2.join();

	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " ms" << endl;
	system("pause");
	return 0;
}

In the above example, the result of this machine VS2013 is as follows (as you can see, the result is uncertain):

Obviously it is not right with the expected result. What's the problem? Yes, multithreading is asynchronous access. Multiple threads access the same resource. At a certain moment, two threads perform num +1 operation at the same time. For example, the current num value of a thread  t1  is 100. When num++ is executed, the time slice ends and another thread  t2  executes. , Operate num++, get num==101, and then execute num++ in another thread  t1  . At this time, the value of num++ (which can be simply understood as num= num +1) is added from 100. Because the num value on the right is 100 at the time, it is a temporary variable and can be given an alias. num++ is converted into an atomic operation, similar to this expression:

int temp = num;//statement 1

num = temp + 1;//statement 2

So the above "simultaneous access" can be understood as: when t1 executes statement 1, the temp value is 100, at this time t2 executes num++, although num==101 is obtained, but the temp value is still 100 at this time, after the operation num It is also equal to 101. Therefore, there will be cases where the final result is less than the expected value.

Know the reason, let's see how to solve it?

 

Method one: add mutex lock

Release the commented mutex lock in the code above

The results are as follows:

The result is correct, but because it takes time to add and unlock, so the efficiency is not high.

 

Method 2: Let the threads execute separately

#include<iostream>
#include<thread>
#include<mutex>

const int N = 100000000;
int num(0);
mutex m;

void run()
{
	for (int i = 0; i < N; i++)
	{
		num++;
	}
}

int main()
{
	clock_t start = clock();

	thread t1(run);
    t1.join();
    
    thread t2(run);
	t2.join();

	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " ms" << endl;
	system("pause");
	return 0;
}

Want to see the result:

When thread t1 executes run(), using join will block the current main thread. That is to say, when the join is executed, all the current threads continue to execute without being affected (the currently started thread continues), and the statement after the join will be blocked, and the subsequent statement will not resume until the thread that currently adds the join ends.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/qq_38915078/article/details/113033841