本次学习相关资料如下:
Boost C++ 库 第 6 章 多线程(大部分代码的来源)
Boost程序库完全开发指南 - 深入C++“准”标准库 第三版 罗剑锋著
头文件:
#include <stdio.h>
#include <string.h>
#include <boost\version.hpp>
#include <boost\config.hpp>
#include <iostream>
#include <boost\thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/cstdint.hpp>
#include <cstdlib>
#include <vector>
#include <random>
首先是如何创建简单的多线程:
int sum = 0;void f_mutex(unsigned int no) {for (int i = 0; i < 10; i++) {sum++;std::cout << "我是线程" << no << ": sum 为" << sum << std::endl;}
}int main()
{std::cout << boost::thread::hardware_concurrency() << std::endl;//这个可以用来显示CPU内核数,就是可以并行的线程数量std::cout << "程序开始" << std::endl;boost::thread a(f_metex,1); //第一个参数是线程要执行的函数,后面是函数所需要的参数boost::thread b(f_metex,2);a.join(); //等待线程结束,如果没有这个的话主线程一下就结束了,子线程也没了b.join();return 0;
}
上面的打印可能会出问题,sum的值不一定就是20,因为cpu在任意时刻都有可能切换线程,当1号线程读取出sum的值,准备加1的时候,cpu可能会切换到2号线程,读取出sum的值并加1,然后再切换回1号线程,这时候1号线程加1的值就不正确了。
所以要控制这个共享资源,出现了互斥量这样的内容。
boost::mutex mu; //这个是互斥量void f_mutex(unsigned int no) {for (int i = 0; i < 10; i++) {mu.lock(); //上锁了,这样就算切换了线程,发现上锁了也不能继续往下执行sum++;std::cout << "我是线程" << no << ": sum 为" << sum << std::endl;mu.unlock(); //执行完后解锁,这样其他线程就可以执行了}
}
这个很好理解,因为有一个线程进入了临界区,所以其他线程必须等待临界区的访问结束后才能进入,所以程序能够正常打印sum为20
当然如果临界区里面出现了异常,导致没有正常解锁,是不是就会影响到其他线程进入临界区呢?所以我们可以使用lock_guard来解决问题。
std::default_random_engine e; //用来生成随机数
/*
线程睡眠函数
*/
void wait(int seconds) {boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}void f_lock_guard() {int i;for (i = 0; i < 10; i++) {boost::lock_guard<boost::mutex> lock(mu); wait(e() % 2); //这个是一个sleep函数,e()就可以生成一个随机数了。sum++;std::cout << "我是线程" << boost::this_thread::get_id() << ": sum 为" << sum << std::endl; //这个get_id可以打印线程号,不需要传入参数来区分线程了。 }std::cout << "我是线程" << boost::this_thread::get_id() << ",我结束了。i :" << i << std::endl;
}int main()
{std::cout << boost::thread::hardware_concurrency() << std::endl;//这个可以用来显示CPU内核数,就是可以并行的线程数量std::cout << "程序开始" << std::endl;boost::thread a(f_lock_guard); //这里改动了,不再需要参数了boost::thread b(f_lock_guard);a.join(); //等待线程结束,如果没有这个的话主线程一下就结束了,子线程也没了b.join();return 0;
}
lock_guard 这个类会在构造函数里调用mu.lock(),析构函数里调用mu.unlock(),这样的话lock就会在离开作用域的时候调用析构函数,从而调用mu.unlock(),退出临界区的占用。
当程序存在多个线程需要读取资源,而只有一个线程会修改资源(即读者-写者问题),使用上面的方法并不能很好地实现。
先来描述一下要求:
(1)可以存在多个线程同时读取资源;
(2)读取资源的时候不可以修改资源;
(3)修改资源的时候不可以读取资源;
为了满足上面3个要求,要增加新类型的锁。
这里f_shared_fill往random_numbers添加随机数,相当于写着;
f_shared_print和f_shared_count 打印随机数和统计随机数,相当于读者。
boost::shared_mutex shared_mu;
vector<int> random_numbers;int sum = 0;
void f_shared_fill() {for (int i = 0; i < 5; ++i) {boost::unique_lock<boost::shared_mutex> lock(shared_mu); //写锁std::cout << "我要写入了" << std::endl;random_numbers.push_back(e()%100);std::cout << "我写完了" << std::endl;lock.unlock();wait(1);}
}void f_shared_print() {for (int i = 0; i < 5; ++i) {wait(1);boost::shared_lock<boost::shared_mutex> lock(shared_mu); //读锁boost::this_thread::yield(); //放弃时间片,让其他线程执行std::cout << "我在打印" << std::endl;std::cout << random_numbers.back() << std::endl;std::cout << "我打印完了" << std::endl;}
}void f_shared_count() {for (int i = 0; i < 5; ++i) {wait(1);boost::shared_lock<boost::shared_mutex> lock(shared_mu); //读锁boost::this_thread::yield(); //放弃时间片,让其他线程执行std::cout << "我在统计" << std::endl;sum += random_numbers.back();std::cout << "我统计完了" << std::endl;}
}int main()
{std::cout << "程序开始" << std::endl;boost::thread a(f_shared_fill);boost::thread b(f_shared_print);boost::thread c(f_shared_count);a.join();b.join();c.join();std::cout << sum << std::endl;return 0;
}
注意写锁和读锁的区别。
boost::unique_lock和boot::lock_guard类似,也是能够在构造函数中锁定,析构函数中解锁,但比boot::lock_guard更复杂。
f_shared_fill函数执行的时候上锁,保证其他线程无法访问。并且在后面有个主动调用lock.unlock的主动解锁,然后进入睡眠,保证读者线程能够执行。
f_shared_print和f_shared_count开头都有一个wait,用来保证f_shared_fill比他们先执行完。然后有一个释放时间片yield,通过这个能够更好地看出在f_shared_print或f_shared_count执行的过程中,cpu能够执行另外的读者线程,之所以不执行f_shared_fill是因为执行yield之前已经加了读锁。
程序是通过wait来控制线程的执行顺序,不会出现读线程执行了2次或以上而写线程未执行或是写线程写入了2次或以上而读线程一次都未执行。
当然可以用更好的方法来解决这个问题,那就是条件变量。
void f_condition_fill() {for (int i = 0; i < 10; ++i) {std::cout << "我是线程1 "<<random_numbers.size()<<std::endl;boost::unique_lock<boost::mutex> lock(mu);random_numbers.push_back(e()%100);cond.notify_all();cond.wait(mu);}
}void f_condition_print() {static int next_size = 1;for (int i = 0; i < 10; ++i) {std::cout << "我是线程2 " << random_numbers.size() << std::endl;boost::unique_lock<boost::mutex> lock(mu); while (next_size != random_numbers.size()) {cond.wait(mu);} std::cout << random_numbers.back() << std::endl;++next_size;cond.notify_all();}
}
int main()
{std::cout << "程序开始" << std::endl;boost::thread a(f_condition_fill);boost::thread b(f_condition_print);a.join();b.join();std::cout << sum << std::endl;return 0;
}
注意到这里用到的互斥量是mutex而不是shared_lock