C++11多线程编程

目录

1.C++11中线程库的使用

2.互斥锁

3.条件变量

4.跨平台线程池

5.异步并发

6.原子操作


1.C++11中线程库的使用

        (1)构造函数:

                thread():构造一个线程对象,没有关联任何线程函数,即没有启动任何线程;

                thread(fn,args1,atgs2,...):构造一个线程对象,并关联线程函数fn,参数为:args1,atgs2,...。fn按照三种方式提供:函数指针,lambda表达式和函数对象。

                thread( thread&& other ) :移动构造,将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。

                thread( const thread& ) = delete:不允许拷贝构造函数一会赋值。

        (2)get_id():获取线程id,this_thread::get_id()

        (4)jionable():用于判断主线程和子线程是否处于关联状态,一般情况下,二者之间的关系处于关联状态,该函数返回一个布尔类型。(true:主线程和子线程之间有关联关系;false:主线程和子线程之间没有关联关系)。

        (5)jion():该函数调用后会阻塞线程,当该线程结束后,主线程继续执行;

        (6)detach():在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的“死活”就与主线程无关。在线程分离之后,主线程退出也会一并销毁创建出的所有子线程,在主线程退出之前,它可以脱离主线程继续独立的运行,任务执行完毕之后,这个子线程会自动释放自己占用的系统资源。

2.互斥锁

               (1)mutex包含了四个互斥量的种类:

                    std::mutex

                        lock():上锁,锁住互斥量;

                        unlock():解锁,释放对互斥量的所有权;

                        try_lock():尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会阻塞。

                        注意:如果当前互斥量(lock和trylock())被当前调用线程锁住,则会产生死锁

                     std::recursive_mutex

                           递归互斥锁允许同一个线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁问题。建议少用,多次使用可能会导致bug的产生或抛出std::system错误。

                     std::timed_mutex

                        是超时独占互斥锁,主要是在获取互斥锁资源时增加了超时等待功能,因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置了一个超时时长,超时后线程就可以解除阻塞去做其他事情了。    

                        与mutex相比,多了两个成员函数:try_lock_for():函数是当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度;try_lock_until():函数是当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点.

                   std::recursive_timed_mutex

                        递归超时互斥锁的使用方式和std::timed_mutex是一样的,只不过它可以允许一个线程多次获得互斥锁所有权,而std::timed_mutex只允许线程获取一次互斥锁所有权。另外,递归超时互斥锁std::recursive_timed_mutex也拥有和std::recursive_mutex一样的弊端,不建议频繁使用。

             (2)lock_guard

                是C++11中定义的模板类。lock_guard类模板主要是通过RALL的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。但是用户没办法对锁进行控制。

        (3)unique_lock

                std::unique_lock 是 C++ 标准库中提供的一个互斥量封装类,用于在多线程程序中对互斥量进行加锁和解锁操作。它的主要特点是可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等。std::unique_lock 提供了以下几个成员函数:

lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。
try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
unlock():对互斥量进行解锁操作。


unique_lock() noexcept = default:默认构造函数,创建一个未关联任何互斥量的 std::unique_lock 对象。

explicit unique_lock(mutex_type& m):构造函数,使用给定的互斥量 m 进行初始化,并对该互斥量进行加锁操作。

unique_lock(mutex_type& m, defer_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,但不对该互斥量进行加锁操作。

unique_lock(mutex_type& m, try_to_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联。

unique_lock(mutex_type& m, adopt_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并假设该互斥量已经被当前线程成功加锁。

 3.条件变量 (condition_variable)  

               condition_variable的成员函数主要分为两部分:线程等待(阻塞)函数和线程通知(唤醒)函数,这些函数被定义于头文件#include<condition_variable>

//调用该函数的线程直接被阻塞
void wait (unique_lock<mutex>& lck);
//该函数的第二个参数是一个判断条件,是一个返回值为布尔类型的函数
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
该参数可以传递一个有名函数的地址,也可以直接指定一个匿名函数
表达式返回false当前线程被阻塞,表达式返回true当前线程不会被阻塞,继续向下执行

         wait_for()函数和wait()的功能是一样的,只不过多了一个阻塞时长,假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。

template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,
                    const chrono::duration<Rep,Period>& rel_time);
	
template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lck,
               const chrono::duration<Rep,Period>& rel_time, Predicate pred);

        通知函数:

         notify_one():唤醒一个被当前条件变量阻塞的线程
         notify_all():唤醒全部被当前条件变量阻塞的线程

4.跨平台线程池

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

class ThreadPool {
public:
    ThreadPool(int numThreads) : stop(false) {
        for (int i = 0; i < numThreads; ++i) {
            threads.emplace_back([this] {
                while (true) {
                    std::unique_lock<std::mutex> lock(mutex);
                    condition.wait(lock, [this] { return stop || !tasks.empty(); });
                    if (stop && tasks.empty()) {
                        return;
                    }
                    std::function<void()> task(std::move(tasks.front()));
                    tasks.pop();
                    lock.unlock();
                    task();
                }
                });
        }
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& thread : threads) {
            thread.join();
        }
    }

    template<typename F, typename... Args>
    void enqueue(F&& f, Args&&... args) {
        std::function<void()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
        {
            std::unique_lock<std::mutex> lock(mutex);
            tasks.emplace(std::move(task));
        }
        condition.notify_one();
    }

private:
    std::vector<std::thread> threads;
    std::queue<std::function<void()>> tasks;
    std::mutex mutex;
    std::condition_variable condition;
    bool stop;
};

int main() {
    ThreadPool pool(4);
    for (int i = 0; i < 8; ++i) {
        pool.enqueue([i] {
            std::cout << "Task " << i << " is running in thread " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(5));
            std::cout << "Task " << i << " is done" << std::endl;
            });
    }
    return 0;
}

5.异步并发(async future package_task promise)

        async()采用的头文件是#include<future>

        如下例所示:

#include<iostream>
#include<future>
using namespace std;
int func()
{
	int i = 0;
	for (i = 0; i < 1000; i++)
	{
		i++;
	}
	return i;
}
int main()
{
	future<int> future_result = async(launch::async, func);
	cout << func() << endl;
	cout << future_result.get() << endl;

	return 0;
}

 async中的func()函数相当于运行在另一个线程中。

        packaged_task():是一个类模板,用于将一个可调对象(如函数、函数对象或Lambda表达式)封装成一个异步操作,并返回一个std::future对象,表示异步操作的结果。package_task可以方便地一个函数或可调用对象转换成一个异步操作,供其他线程使用。

packaged_task<int()> task(func);
	auto future_result = task.get_future();
	thread t1(move(task));
	t1.join();
	cout << func() << endl;
	cout << future_result.get() << endl;

        promise:是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和async一起使用,用于实现异步编程。

void func(promise<int> f)
{
	f.set_value(1000);
}
int main()
{
	promise<int> f;
	auto future_result = f.get_future();
	thread t1(func, move(f));
	t1.join();
	cout << future_result.get() << endl;
	return 0;
}

6.原子操作(atomic)

        atomic是c++11标准库中的一个模板,用于实现多线程环境下的原子操作。它提供了一种线程安全的方式来访问和修改共享变量,可以避免多线程环境中的数据竞争问题。

        优势:原子性操作比自己手动加锁和解锁去保证线程安全的效率是更高的。

        常用操作:

        (1)load():将变量的值加载到当前线程的本地缓存中,并返回这个值;

        (2)store(val):将“val”的值存储到atomic变量中,并保证这个操作是原子性的;

        (3)exchange(val):将val的值存储到atomic变量中,并返回原先的值;

        (4)compare_exchange_weak(expected,val)和compare_exchange_strong(expected,val):比较stomic变量和值和expected的值是否相同,如何相同,则将val的值存储到atomic变量中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值