C++进阶(应用篇)—第一章(完) C++11 多线程编程

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)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值