赞
踩
C++11之前,C++库中没有提供和线程相关的类或者接口,因此在编写多线程程序时,Windows上需要调用CreateThread创建线程,Linux下需要调用clone或者pthread_create来创建线程,但是这样是直接调用了系统相关的API函数,编写的代码,无法做到跨平台编译运行。
C++11之后提供了thread线程类,可以很方便的编写多线程的程序。
std::thread定义了四种构造函数:
- thread() noexcept;
-
- template< class Function, class... Args >
- explicit thread( Function&& f, Args&&... args );
-
- thread( thread&& other ) noexcept;
-
- thread( const thread& ) = delete;
随便提一下,当你创建了一个带参(非空的)线程对象时,该线程就会立即执行,不需要显式的调用start
或者run
。
举个例子,下面展示了四个构造函数的使用
- #include <iostream>
- #include <utility>
- #include <thread>
- #include <chrono>
- using namespace std;
-
- void f1(int n)
- {
- for (int i = 0; i < 5; ++i) {
- std::cout << "Thread 1 executing\n";
- ++n;
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
- };
-
- 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(10));
- }
- };
-
- class foo
- {
- public:
- void bar()
- {
- for (int i = 0; i < 5; ++i) {
- std::cout << "Thread 3 executing\n";
- ++n;
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
- }
- int n = 0;
- };
-
- class baz
- {
- public:
- void operator()()
- {
- for (int i = 0; i < 5; ++i) {
- std::cout << "Thread 4 executing\n";
- ++n;
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
- }
- int n = 0;
- };
-
- int main()
- {
- int n = 0;
- foo f;
- baz b;
- std::thread t1([] {
- cout << "Hello World from lambda thread." << endl;
- }); // 线程会立即执行
-
- std::thread t2(f1, n + 1); // 值传递
- std::thread t3(f2, std::ref(n)); //引用传递
- std::thread t4(std::move(t3)); // 移动构造函数,t4 is now running f2(). t3 is no longer a thread
- std::thread t5(&foo::bar, &f); // 在类的成员函数,t5 runs foo::bar() on object f
- std::thread t6(b); // 使用仿函数,t6 runs baz::operator() on a copy of object b
- //std::thread t6(std::ref(b));
-
- // 等待线程t2和t4,t5,t6执行完,main线程再继续运行
- t2.join();
- t4.join();
- t5.join();
- t6.join();
- std::cout << "Final value of n is " << n << '\n';
- std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
- std::cout << "Final value of b.n (baz::n) is " << b.n << '\n'; // 因为是复制对象,原对象的n不改变
-
- return 0;
- }
-
注解:
join:等待子线程,调用线程main处于阻塞模式。直到由 子线程线程执行完毕, join 才返回,join()执行完成之后,底层线程id被设置为0,即joinable()变为false。
joinable:检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。
detach: 将当前线程对象所代表的执行实例与该线程对象分离,不会阻塞当前main线程,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。
- void foo(int n)
- {
- std::cout << "start run " << endl;
- std::cout << "run " << n << endl;
- }
-
- int main()
- {
- std::thread t1(foo, 1);
- if (t1.joinable())
- {
- std::cout << "thread joinable" << std::endl;
- }
-
- std::cout <<"this_thread调用get_id "<< std::this_thread::get_id() << std::endl;
- std::cout << "t1调用get_id " << t1.get_id() << std::endl;
- t1.detach();
-
- // detach之后,joinable返回false,get_id返回0x0
- if (!t1.joinable())
- {
- std::cout << "t1 can not joinable" << std::endl;
- }
-
- std::cout << "t1调用get_id " <<t1.get_id() << std::endl;
- return 0;
- }
运行结果
在多线程环境中运行的代码段,需要考虑是否存在竞态条件。如果存在竞态条件,我们就说该代码段不是线程安全的,是不能直接运行在多线程环境当中。对于这样的代码段,我们经常称之为临界区。临界区又称关键代码段,指的是一小段代码在代码执行前,需要独占一些资源资源,比如访问CPU,打印机等。对于临界区资源,多线程环境下需要保证它以原子操作执行,要保证临界区的原子操作,就需要用到线程间的互斥操作-锁机制。
- int g_i = 0;
- mutex g_mutex;
- void c()
- {
- for (int i = 0; i < 3; ++i)
- {
- g_mutex.lock();
- cout << std::this_thread::get_id() << " " << g_i++ << endl;
- g_mutex.unlock();
- }
- }
-
- int main()
- {
- thread t1(c);
- thread t2(c);
-
- if (t1.joinable())
- t1.join();//调用该函数会阻塞当前线程,直到该t1线程执行完成
- if (t2.joinable())
- t2.join();
- return 0;
- }
mutex互斥锁毕竟比较重,对于系统消耗有些大,C++11的thread类库提供了针对简单类型的原子操作类,如std::atomic_int,atomic_long,atomic_bool等,它们值的增减都是基于CAS操作的,既保证了线程安全,效率还非常高。
下面代码示例开启10个线程,每个线程对整数增加1000次,保证线程安全的情况下,应该加到10000次,这种情况下,可以用atomic_int来实现,代码示例如下:
- #include <iostream>
- #include <atomic> // C++11线程库提供的原子类
- #include <thread> // C++线程类库的头文件
- #include <vector>
-
- // 原子整形,CAS操作保证给sum自增自减的原子操作
- std::atomic_int sum = 0;
-
- // 线程函数
- void sumTask()
- {
- // 每个线程给sum加1000次
- for (int i = 0; i < 1000; ++i)
- {
- sum++;
- }
- };
-
- int main()
- {
- // 创建10个线程放在容器当中
- std::vector<std::thread> vec;
- for (int i = 0; i < 10; ++i)
- {
- vec.push_back(std::thread(sumTask));
- }
-
- // 等待线程执行完成
- for (int i = 0; i < vec.size(); ++i)
- {
- vec[i].join();
- }
-
- // 所有子线程运行结束,sum的结果每次运行应该都是10000
- std::cout << "sum : " << sum << std::endl;
-
- return 0;
- }
多线程在运行过程中,各个线程都是随着操作系统的调度算法,占用CPU时间片来执行指令做事情,每个线程的运行完全没有顺序可言。但是在某些应用场景下,一个线程需要等待另外一个线程的运行结果,才能继续往下执行,这就需要涉及线程之间的同步通信机制。
线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;如果消费者线程去消费产品,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行。
C++11 线程库提供的条件变量condition_variable,就是Linux平台下的Condition Variable机制,用于解决线程间的同步通信问题,下面通过代码演示一个生产者-消费者线程模型,仔细分析代码:
- #include <iostream> // std::cout
- #include <thread> // std::thread
- #include <mutex> // std::mutex, std::unique_lock
- #include <condition_variable> // std::condition_variable
- #include <vector>
-
- // 定义互斥锁(条件变量需要和互斥锁一起使用)
- std::mutex mtx;
- // 定义条件变量(用来做线程间的同步通信)
- std::condition_variable cv;
- // 定义vector容器,作为生产者和消费者共享的容器
- std::vector<int> vec;
-
- // 生产者线程函数
- void producer()
- {
- // 生产者每生产一个,就通知消费者消费一个
- for (int i = 1; i <= 10; ++i)
- {
- // 获取mtx互斥锁资源
- std::unique_lock<std::mutex> lock(mtx);
-
- // 如果容器不为空,代表还有产品未消费,等待消费者线程消费完,再生产
- while (!vec.empty())
- {
- // 判断容器不为空,进入等待条件变量的状态,释放mtx锁,
- // 让消费者线程抢到锁能够去消费产品
- cv.wait(lock);
- }
- vec.push_back(i); // 表示生产者生产的产品序号i
- std::cout << "producer生产产品:" << i << std::endl;
-
- /*
- 生产者线程生产完产品,通知等待在cv条件变量上的消费者线程,
- 可以开始消费产品了,然后释放锁mtx
- */
- cv.notify_all();
-
- // 生产一个产品,睡眠100ms
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
- }
- // 消费者线程函数
- void consumer()
- {
- // 消费者每消费一个,就通知生产者生产一个
- for (int i = 1; i <= 10; ++i)
- {
- // 获取mtx互斥锁资源
- std::unique_lock<std::mutex> lock(mtx);
-
- // 如果容器为空,代表还有没有产品可消费,等待生产者生产,再消费
- while (vec.empty())
- {
- // 判断容器为空,进入等待条件变量的状态,释放mtx锁,
- // 让生产者线程抢到锁能够去生产产品
- cv.wait(lock);
- }
- int data = vec.back(); // 表示消费者消费的产品序号i
- vec.pop_back();
- std::cout << "consumer消费产品:" << data << std::endl;
-
- /*
- 消费者消费完产品,通知等待在cv条件变量上的生产者线程,
- 可以开始生产产品了,然后释放锁mtx
- */
- cv.notify_all();
-
- // 消费一个产品,睡眠100ms
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
- }
- int main()
- {
- // 创建生产者和消费者线程
- std::thread t1(producer);
- std::thread t2(consumer);
-
- // main主线程等待所有子线程执行完
- t1.join();
- t2.join();
-
- return 0;
- }
参考:
C++11 - thread多线程编程,线程互斥和同步通信,死锁问题分析解决_大秦坑王的专栏-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。