1.7 C++11的同步机制
除了win API和C++11提供的同步机制,还可以使用轮循机制。
轮循机制:创建全局变量g_val;当线程1执行完一段代码后,对g_val赋值;线程2对g_val不断的查询,当g_val等于约定好的某个数值时,开始执行线程2中的代码。
使用轮循机制效率较低,因此我们需要学习C++11中的同步机制:条件变量。
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
using std::cout;
using std::endl;
int g_iar[5];
std::mutex mu;
void fun_thread_one()
{
for (int i = 0; i <= 5;++i)
{
std::unique_lock<std::mutex> auto_lock(mu);
g_iar[i] = i;
auto_lock.unlock();
printf("fun_thread_one:g_iar[%d] = %d\n",i,i);
}
}
void fun_thread_two()
{
int val = 0;
for (int i = 0; i <= 5; ++i)
{
std::unique_lock<std::mutex> auto_lock(mu);
val = g_iar[i];
auto_lock.unlock();
printf("fun_thread_two:g_iar[%d] = %d\n", i, val);
}
}
int main(int argc, char ** argv)
{
std::thread thread_one(fun_thread_one);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::thread thread_two(fun_thread_two);
thread_one.join();
thread_two.join();
return 0;
}
运行结果:
fun_thread_one:g_iar[0] = 0
fun_thread_one:g_iar[1] = 1
fun_thread_one:g_iar[2] = 2
fun_thread_one:g_iar[3] = 3
fun_thread_one:g_iar[4] = 4
fun_thread_one:g_iar[5] = 5
fun_thread_two:g_iar[0] = 0
fun_thread_two:g_iar[1] = 1
fun_thread_two:g_iar[2] = 2
fun_thread_two:g_iar[3] = 3
fun_thread_two:g_iar[4] = 4
fun_thread_two:g_iar[5] = 5
上述代码实现线程1和线程2,线程1对全局缓冲区写,线程2对全局缓冲区读;线程1每写一次,线程2读一次。但是从运行结果来看,明显失败了。
使用条件变量,在线程1写完一次后,唤醒线程2进行读,线程堵塞休眠;线程2一直休眠,等待线程1写完后唤醒它。
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
using std::cout;
using std::endl;
int g_iar[108];
std::mutex mu;
std::condition_variable cv;
bool ready = false;
void fun_thread_one()
{
for (int i = 0; i <= 5; ++i)
{
std::unique_lock<std::mutex> auto_lock(mu);
g_iar[i] = i;
printf("fun_thread_one:g_iar[%d] = %d\n", i, i);
ready = true;
auto_lock.unlock();
cv.notify_one();
//cv.notify_all();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void fun_thread_two()
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
int val = 0;
for (int i = 0; i <= 5; ++i)
{
std::unique_lock<std::mutex> auto_lock(mu);
cv.wait(auto_lock, []() { return ready; });
val = g_iar[i];
printf("fun_thread_two:g_iar[%d] = %d\n", i, val);
ready = false;
auto_lock.unlock();
}
}
int main(int argc, char ** argv)
{
std::thread thread_one(fun_thread_one);
std::thread thread_two(fun_thread_two);
thread_one.join();
thread_two.join();
return 0;
}
运行结果:
fun_thread_one:g_iar[0] = 0
fun_thread_two:g_iar[0] = 0
fun_thread_one:g_iar[1] = 1
fun_thread_two:g_iar[1] = 1
fun_thread_one:g_iar[2] = 2
fun_thread_two:g_iar[2] = 2
fun_thread_one:g_iar[3] = 3
fun_thread_two:g_iar[3] = 3
fun_thread_one:g_iar[4] = 4
fun_thread_two:g_iar[4] = 4
fun_thread_one:g_iar[5] = 5
fun_thread_two:g_iar[5] = 5
条件变量使用方法:1.创建condition_variable全局变量;2.线程1代码执行完后,执行notify_one()唤醒一个休眠的线程(notify_all()唤醒所有休眠的线程);3.线程2执行wait(),一直等待条件变量的唤醒。
notify_one()函数表示唤醒一个等待线程;notify_all()函数表示唤醒所有等待线程。notify函数不能加锁,即解锁后才可以使用。
wait()函数有两种形式:void wait (unique_lock<mutex>& lck)和template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred)。由于可能出现虚假唤醒的情况,常使用第二种形式的wait()函数。形参1—unique_lock:wait()函数是放入unique_lock锁中,由于在加锁的代码中不允许休眠,因此wait()函数会对unique_lock先解锁,等条件变量唤醒时再加锁(注:参数1不能是lock_guard)。形参2—Predicate:只有当pred条件为false时,wait()函数才会阻塞当前线程;当其他线程的唤醒后,只有当pred为true时才会被解除阻塞。
注:使用条件变量,需要头文件mutex。
1.8 C++11的参数传递
全局变量、堆空间是所有线程的共享资源;创建线程时,可以用move()和ref()函数进行参数传递。还有别的办法在主线程和子线程间传递数据吗?
1.8.1返回值传递
如何将子线程的返回值传递给主线程呢?
方法1:async
#include <iostream>
#include <future>
using std::cout;
using std::endl;
int fun_thread_add(int a)
{
int sum = 0;
for (int i = 1; i <= a; ++i)
{
sum += i;
}
return sum;
}
int main(int argc, char ** argv)
{
/*std::thread thread_one(fun_thread_add);
thread_one.join();*/
std::future<int> fut = std::async(std::launch::async, fun_thread_add,5);
//当线程执行完之后,才可以get,因此使用async创建的线程不需要join
cout<<"main: result = "<<fut.get()<<endl;
return 0;
}
运行结果:
main: result = 15
async()函数创建一个子线程去执行线程函数,线程函数返回值是std::future<T>,可以在主线程中使用get()函数获得子线程函数的返回值。async()函数的形参1表示启动策略,std::launch::async表示线程立即执行;std::launch::deferred表示在主线程执行get时,再去执行子线程,即线程延时启动;std::launch::async|std::launch::deferred表示默认行为,即可以立即启动或延时启动,取决于系统的负载。async()函数创建子线程的方法同thread。
使用async()函数和future<T>变量,需要头文件future。
方法2:packaged_task
#include <iostream>
#include <future>
using std::cout;
using std::endl;
int fun_thread_add(int a)
{
int sum = 0;
for (int i = 1; i <= a; ++i)
{
sum += i;
}
return sum;
}
int main(int argc, char ** argv)
{
//std::future<int> fut = std::async(std::launch::async, fun_thread_add, 5);
//当线程执行完之后,才可以get,因此使用async创建的线程不需要join
//cout << "fun_thread_add: result = " << fut.get() << endl;
std::packaged_task<int(int)> pack(fun_thread_add);
std::future<int> fut = pack.get_future();
std::thread thread_one(std::move(pack),5);//std::thread thread_one(std::ref(pack),5)
int val = fut.get();
cout << "main:result = " << val << endl;
thread_one.join();
return 0;
}
或
#include <iostream>
#include <future>
using std::cout;
using std::endl;
int fun_thread_add(int a)
{
int sum = 0;
for (int i = 1; i <= a; ++i)
{
sum += i;
}
return sum;
}
int main(int argc, char ** argv)
{
//std::future<int> fut = std::async(std::launch::async, fun_thread_add, 5);
//当线程执行完之后,才可以get,因此使用async创建的线程不需要join
//cout << "fun_thread_add: result = " << fut.get() << endl;
std::packaged_task<int()> pack(std::bind(fun_thread_add,5));
std::future<int> fut = pack.get_future();
std::thread thread_one(std::move(pack));
int val = fut.get();
cout << "main:result = " << val << endl;
thread_one.join();
return 0;
}
运行结果:
main:result = 15
packaged_task<...>包装一个可调用的对象,并且允许异步获取该对象产生的结果。packaged_task可调用get_future来获得future变量;然后move(packaged_task)或ref(packaged_task)给线程,开始执行packaged_task包装的对象;线程执行完后,future可以get获取线程执行结果。
std::bind()可以将参数绑定到一个function上,减少了函数的参数调用。
1.8.2参数传递
如何将主线程中的参数,延时传递给子线程。
#include <iostream>
#include <future>
using std::cout;
using std::endl;
void fun_thread_add(std::future<int> & fu)
{
int a = fu.get(); //等待主线程赋值
int sum = 0;
for (int i = 1; i <= a; ++i)
{
sum += i;
}
cout << "fun_thread_add:sum = " << sum << endl;
}
int main(int argc, char ** argv)
{
std::promise<int> pro;
std::future<int> fut = pro.get_future();
std::thread thread_one(fun_thread_add,std::ref(fut));//必须用引用传递
pro.set_value(5);//给子线程的参数赋值
thread_one.join();
return 0;
}
运行结果:
fun_thread_add:sum = 15
先建立promise变量,可以理解为“承诺”,promise获取未来参数的数值并传递给future;创建线程时,将future作为参数引用传递给线程函数;最后在主线程中promise赋值,子线程中future获取值。若promise不执行set_value,则子线程会抛出异常(broken_promise)。
在创建多个线程同时使用一个future变量,则
std::shared_future<int> shar_f = fut.share();
std::thread thread_one(fun_thread_add,shar_f ); //必须数值传递
std::thread thread_two(fun_thread_add,shar_f );
...
void fun_thread_add(std::shared_future<int> shar_fu)
{...}
1.9 C++11的原子操作
#include <iostream>
#include <thread>
#include <atomic>
using std::cout;
using std::endl;
std::atomic_int g_sum = { 0 }; //不要写成std::atomic_int g_sum = 0,因为int不能转换为atomic_int
void fun_thread_add(int a)
{
for (int i = 1; i <= a; ++i)
g_sum += i;
cout << "fun_thread_add:g_sum = " << g_sum << endl;
}
int main(int argc, char ** argv)
{
std::thread thread1(fun_thread_add, 5);
thread1.join();
return 0;
}
运行结果:
fun_thread_add:g_sum = 15
bool对应的原子类型为atomic_bool;char对应atomic_char;int对应atomic_int,unsigned int对应atomic_uint;short和long同int,无double;long_long对应atomic_llong,unsigned long_long对应atomic_ullong。
以上所有的原子类型都支持读、写、交换操作。如:
std::atomic<int> g_val = { 0 };
int a = g_val; // 相当于int a = g_val.atomic_load()
g_val = 1; // 相当于g_val.atomic_store(1)
int b = g_val.exchange(1); // g_val赋值为1,b等于g_val原来的值
除此之外,int、short、long、long_long对应的原子类型,还支持加、减操作。如:
std::atomic<int> g_val = { 0 };
g_val += 5; // 相当于g_val.atomic_fetch_add(5)
g_val -= 4; // 相当于g_val.atomic_fetch_sub(4)