c++11新特性的线程相关

1、thread

在c++11之后,标准库有对于子线程的支持。要运用thread就要链接线程库,编译的时候要添加选项“-lpthread”。

thread就是用于多线程编程的。thread的基本用法如下:

#include <iostream>
#include <thread>

void fun1(int n)
{
    std::cout << "fun1" << std::endl;
}

void fun2(int & n)
{
    std::cout << "fun2" << std::endl;
}

void fun4(int n)
{
    std::cout << "fun4" << std::endl;
}

class Example
{
public:
    void operator()(int n)
    {
        std::cout << "Example::operator" << std::endl;
    }

    void fun3(int n)
    {
        std::cout << "Example::fun3" << std::endl;
    }
};

int main(int argc, char ** argv)
{
    //空的thread对象
    std::thread t0;

    int n = 1;

    //参数传值
    std::thread t1(fun1, n);
    //等待t1的线程运行完毕再往下执行
    t1.join();

    //参数传引用
    std::thread t2(fun2, std::ref(n));
    t2.join();

    Example example;

    //可调用对象
    std::thread t_exp(example, n);
    t_exp.join();

    //成员函数调用
    std::thread t3(&Example::fun3, &example, n);
    t3.join();

    //线程转移,t4变成空对象,t5拥有了原来的t4的线程。
    std::thread t4(fun4, n);
    std::thread t5(std::move(t4));

    //线程从t5中分离处理独立运行,调用后t5不再占有线程,而且线程交给了后台,作为守护线程存在
    t5.detach();

    return 0;
}

2、互斥量

互斥量有好几种:mutex、timed_mutex、recursive_mutex、recursive_timed_mutex、shared_mutex(c++17)、shared_timed_mutex(c++14)。

用法:

#include <iostream>
#include <thread>
#include <mutex>
 
int g_num = 0;
std::mutex g_num_mutex;
 
void increase(const int times)
{
    for (int i = 0; i < times; i++)
    {
        //锁住临界区
        std::lock_guard<std::mutex> guard(g_num_mutex);
        g_num++;
    }
}

void decrease(const int times)
{
    for (int i = 0; i < times; i++)
    {
        //锁住临界区
        std::lock_guard<std::mutex> guard(g_num_mutex);
        g_num--;
    }
}
 
int main() 
{
    std::thread inc(increase, 10000000);
    std::thread dec(decrease, 10000);
    inc.join();
    dec.join();
 
    std::cout << g_num << '\n';
}

timed_mutex与mutex大同小异,timed_mutex提供了try_lock_for和try_lock_until用于限制阻塞时间,在阻塞时间里面获取临界区资源。

recursive_mutex和recursive_timed_mutex的关系与mutex和timed_mutex的关系相同,不过带“recursive_”前缀的是一个可递归的互斥量。同一个线程可以多次调用lock/try_lock而不会出现死锁情况。

shared_mutex和shared_timed_mutex与mutex和timed_mutex一样的类似。“shared_”前缀的互斥量提供了一种可以多线程同时读的一种访问方式。

3、条件变量condition_variable、condition_variable_any

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

std::condition_variable cv;
std::mutex m;
int i = 10;

void ZeroHandle()
{
    std::unique_lock<std::mutex> ul(m);
    if (0 != i)
    {
        cv.wait(ul, [](){return 0 == i;});
    }

    std::cout << "ZeroHandle====>" << std::endl;
}

void SetZero()
{
    while (true)
    {
        std::lock_guard<std::mutex> lg(m);
        i--;
        std::cout << "SetZero====>" << i << std::endl;
        if (0 == i)
        {
            cv.notify_one();
            break;
        }
    }
}

int main()
{
    std::thread t1(ZeroHandle);
    std::thread t2(SetZero);

    t1.join();
    t2.join();
	
  	return 0;
}

以上代码展现了条件变量的使用,ZeroHandle函数需要当i为0的时候才能处理,所以要等待SetZero函数把i设置成0的时候才能运行,这个时候用条件变量来唤醒之前等待的线程。condition_variable_any与condition_variable的区别在于condition_variable只能与unique_lock对象来关联,但是condition_variable_any可以与拥有成员函数void lock()与void unlock()作用的对象关联。

4、lock_guard与unique_lock

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

class LockGuardObj
{
public:
	LockGuardObj(int i):m_i(i){}
	
	friend void swap(LockGuardObj & l, LockGuardObj & r)
	{
        /*如果不用std::lock而是直接用没有第二参数的lock_guard来构造lk和rk的时候,
        当多线程对同一对对象调用这个函数且函数参数互换的时候,可能会导致死锁。*/
		std::lock(l.m, r.m);
		std::lock_guard<std::mutex> lk(l.m, std::adopt_lock);
		std::lock_guard<std::mutex> rk(r.m, std::adopt_lock);
		int tmp = l.m_i;
		l.m_i = r.m_i;
		r.m_i = tmp;
	}
	
	void print(){ std::cout << m_i << std::endl; }

private:
	int m_i;
	std::mutex m;
};

class UniqueLockObj
{
public:
	UniqueLockObj(int i):m_i(i){}
	
	friend void swap(UniqueLockObj & l, UniqueLockObj & r)
	{
		std::unique_lock<std::mutex> lk(l.m, std::defer_lock);
		std::unique_lock<std::mutex> rk(r.m, std::defer_lock);
		std::lock(lk, rk);
		int tmp = l.m_i;
		l.m_i = r.m_i;
		r.m_i = tmp;
	}
	
	void print(){ std::cout << m_i << std::endl; }

private:
	int m_i;
	std::mutex m;
};

void LockGuardSwap(LockGuardObj & lg1, LockGuardObj & lg2, int i)
{
    std::cout << "LockGuardObj swap before===>" << i << std::endl;
	lg1.print();
	lg2.print();
	swap(lg1, lg2);
	std::cout << "LockGuardObj swap after===>" << i  << std::endl;
	lg1.print();
	lg2.print();
}

void UniqueLockSwap(UniqueLockObj & ul1, UniqueLockObj & ul2, int i)
{
    std::cout << "UniqueLockObj swap before===>" << i  << std::endl;
	ul1.print();
	ul2.print();
	swap(ul1, ul2);
	std::cout << "UniqueLockObj swap after===>" << i  << std::endl;
	ul1.print();
	ul2.print();
}

int main()
{
	LockGuardObj lg1(1);
	LockGuardObj lg2(2);
    std::thread t1(LockGuardSwap, std::ref(lg1), std::ref(lg2), 1);
    std::thread t2(LockGuardSwap, std::ref(lg2), std::ref(lg1), 2);
		
	UniqueLockObj ul1(1);
	UniqueLockObj ul2(2);
    std::thread t3(UniqueLockSwap, std::ref(ul1), std::ref(ul2), 3);
    std::thread t4(UniqueLockSwap, std::ref(ul2), std::ref(ul1), 4);

    t1.join();
    t2.join();
    t3.join();
    t4.join();
	
  	return 0;
}

以上代码展示了lock_guard和unique_lock对于同一效果的不同。对于lock_guard的使用,这个模板类的使用在构造的时候必须拥有并锁上一个互斥量,如果用一个已经锁上的互斥量作为构造函数的参数,那么必须在构造函数的第二个参数传入std::adopt_lock,表示这个互斥量已经上锁了。对于unique_lock的使用,这个模板类提供了一个不需要对互斥量上锁的锁对象,通过在构造函数传入std::defer_lock来实现,把对互斥量上锁的动作延迟到之后有需要的地方去调用。同时unique_lock是可以move的对象,所以用一些函数获取特定的锁对象进行函数之间的传递,有需要的就可以对这个锁对象上锁。

7、call_once和once_flag的使用

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

std::once_flag flag;

//只会调用一次的函数
void OnceFun()
{
    std::cout << "OnceFun" << std::endl;
}

void Fun()
{
    std::cout << "Fun" << std::endl;
    std::call_once(flag, OnceFun);
}

int main()
{
    std::thread thread1(Fun);
    std::thread thread2(Fun);
    std::thread thread3(Fun);
    std::thread thread4(Fun);

    thread1.join();
    thread2.join();
    thread3.join();
    thread4.join();
}

call_once与once_flag可以配合使用,使得call_once关联的函数只调用一次后就把once_flag标志设置了。如果调用call_once的时候once_flag标志已经设置了,那么call_once关联的函数就不会调用而且call_once立即返回。如果call_once在调用关联函数的时候抛出异常,那么异常会传播到call_once的调用者。

8、function和bind的使用

#include <iostream>
#include <thread>
#include <functional>

void Fun(int arg1, int arg2, int arg3)
{
    std::cout << "Fun arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
}

struct X
{
    int i;
    
    void Fun(int arg1, int arg2, int arg3)
    {
        std::cout << "X::Fun " << i << " arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
    }
    
    void FunConst(int arg1, int arg2, int arg3) const
    {
        std::cout << "X::FunConst " << i << " arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
    }
};

using namespace std::placeholders;

int main()
{
    //关联普通函数
    std::function<void(int, int, int)> F = Fun;
    //关联成员函数
    std::function<void(X &, int, int, int)> XF = &X::Fun;
    std::function<void(const X &, int, int, int)> XFC = &X::FunConst;

    F(1, 2, 3);

    X x;
    x.i = 9999;

    XF(x, 4, 5, 6);
    XFC(x, 7, 8, 9);

    //绑定普通函数,10与11是固定参数,占位符std::placeholders::_1是返回的可调用对象的第一个参数
    //BindF是std::function<void(int)>类型对象
    auto BindF = std::bind(F, 10, 11, _1);
    BindF(12);

    //绑定成员函数
    //BindXF 是std::function<void()>类型对象
    auto BindXF = std::bind(&X::Fun, x, 13, 14, 15);
    BindXF();

    //占位符的应用
    //BindXFC 是std::function<void(int, int, int, cosnt X &)>类型对象
    auto BindXFC = std::bind(XFC, _4, _3, _2, _1);
    BindXFC(16, 17, 18, x);
}

9、异步操作future、async、packaged_task、promise

#include <iostream>
#include <thread>
#include <future>

int Fun(int arge)
{
    return arge;
}

struct X
{
    int i;
    
    int Fun()
    {
        std::cout << i << " obj:" << this <<std::endl;
        return i;
    }
};

int main()
{
    //一般用法
    auto f1 = std::async(Fun, 1);

    //以指针方式传递对象来调用对象的成员函数作为任务。
    X x2;
    x2.i = 2;
    std::cout << "x2 obj:" << &x2 << std::endl; 
    auto f2 = std::async(&X::Fun, &x2);

    //以对象的copy传递对象来调用对象的成员函数作为任务。
    X x3;
    x3.i = 3;
    std::cout << "x3 obj:" << &x3 << std::endl; 
    auto f3 = std::async(&X::Fun, x3);

    std::cout << f1.get() << std::endl;
    std::cout << f2.get() << std::endl;
    std::cout << f3.get() << std::endl;
}

future与async配合使用可以启动一个异步任务,任务的返回值会保存到future中。如果任务抛出异常,异常会保存到future中。async可以通过传递std::launch类型的枚举来控制运行关联函数的策略,std::launch::async是立刻调用新线程执行关联函数,std::launch::deferred是调用方线程上首次请求关心函数的返回值的时候才执行关联函数。std::launch::async | std::launch::deferred是根据具体实现来决定应该怎么启动(默认的方式)。

#include <iostream>
#include <thread>
#include <functional>
#include <future>
#include <chrono>

void Fun(int arg1, int arg2, int arg3)
{
    for(int i = 0; i < 3; i++)
    {
        std::chrono::seconds sec(1);
        std::this_thread::sleep_for(sec);
        std::cout << "FunLoop " << i << std::endl;
    }
    std::cout << "Fun arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
}

struct X
{
    int i;
    
    void Fun(int arg1, int arg2, int arg3)
    {
        for(int i = 0; i < 3; i++)
        {
            std::chrono::seconds sec(1);
            std::this_thread::sleep_for(sec);
            std::cout << "X::FunLoop " << i << std::endl;
        }
        std::cout << "X::Fun " << i << " arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
    }
    
    void FunConst(int arg1, int arg2, int arg3) const
    {
        for(int i = 0; i < 3; i++)
        {
            std::chrono::seconds sec(1);
            std::this_thread::sleep_for(sec);
            std::cout << "X::FunConstLoop " << i << std::endl;
        }
        std::cout << "X::FunConst " << i << " arg1:" << arg1 << ", arg2:" << arg2 << ", arg3:" << arg3 << std::endl;
    }
};

int main()
{
    std::function<void(int, int, int)> F = Fun;
    std::function<void(X &, int, int, int)> XF = &X::Fun;
    std::function<void(const X &, int, int, int)> XFC = &X::FunConst;

    std::packaged_task<void(int, int, int)> task_F(F);
    std::packaged_task<void(X &, int, int, int)> task_XF(XF);
    std::packaged_task<void(const X &, int, int, int)> task_XFC(XFC);

    X x;
    x.i = 99999;

    //异步执行task_F
    auto thread_F = std::thread(std::move(task_F), 1, 2, 3);

    //与函数一样执行task_XF与task_XFC
    task_XF(x, 4, 5, 6);
    task_XFC(x, 7, 8, 9);

    thread_F.join();
}

packaged_task将一个future绑定到一个函数或可调用对象上,是一个任务的包装。可以通过组织多个packaged_task来组织多个任务。packaged_task是一个可调用对象,当调用packaged_task对象的时候关联的任务就会调用。packaged_task可以通过thread异步开启任务。

#include <iostream>
#include <thread>
#include <functional>
#include <future>
#include <chrono>

void Fun(std::promise<int> acc, std::promise<bool> is_over_half)
{
    const int TOTAL = 10000;
    int result = 0;
    for(int i = 0; i < TOTAL; i++)
    {
        if (i == TOTAL / 2)
        {
            is_over_half.set_value(true);

            std::chrono::seconds sec(5);
            std::this_thread::sleep_for(sec);
        }

        result++;
    }

    acc.set_value(result);
}

int main()
{
    //一个任务两个结果
    std::promise<int> acc;
    std::promise<bool> is_over_half;

    std::future<int> future_acc = acc.get_future();
    std::future<bool> future_is_over_half = is_over_half.get_future();

    //通过传入promise参数来获取Fun任务的两个结果。
    auto thread = std::thread(Fun, std::move(acc), std::move(is_over_half));

    std::cout << "is_over_half:" << future_is_over_half.get() << std::endl;
    std::cout << "acc:" << future_acc.get() << std::endl;

    thread.join();
}

future与promise配合使用可以使得一个任务设置多个值提供可能。一个promise对应一个future,任务可以通过传递多个promise作为参数来给任务提供设置多个promise的方式,从而使得promise对应的future变得可用。

10、可变参数模板

#include <iostream>

template<typename T>
void Print(const T & t)
{
    std::cout << t << std::endl;
}

template<typename ... ArgsT> //类型包名字
void Fun(ArgsT ... args) //形参包名字
{
    //sizeof...的使用
    std::cout << "sizeof:" << sizeof...(args) << std::endl;

    //可变模板参数扩展(Print(args), 0)是一个模式,模式加...是扩展这个模式。
    //相当于int arr[] = {(Print("KarlChan"), 0),(Print(1), 0), (Print(2), 0)};
    int arr[] = {(Print(args), 0)...};
}

int main()
{
    Fun("KarlChan", 1, 2);
}

可变参数模板的类型包生命格式是template<typename ... ArgsT>,其中ArgsT是类型包名字。

利用类型包作为形参类型声明函数的方式是ArgsT ... args,其中ArgsT是类型包名字,args是形参包名字。

sizeof...(args)返回的是实参的数量。

可以在表达式express(args)...来表达,对args的扩展。这表示对于args中的每个参数,都会执行express表达式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值