当前位置: 首页 > article >正文

c++ 线程库

线程

        在编程中,线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。线程自身不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可以与同属进程的其他线程共享进程所拥有的全部资源。

        使用线程,可以实现一段程序不同的地方并发的执行,极大的提高运行效率

        在c++11之前,线程库主要由操作系统或第三方软件提供,linux下为pthread库,windows下为Win32 API ,这样的代码可移植性较差,c++11引入了线程库thread ,这样做可以把可移植性问题交给不同平台的编译器,减少了出问题的概率.

创建线程与线程赋值

        线程不支持拷贝构造和拷贝赋值

#include <thread>
void fun(string str)
{
    cout << str << endl;
}
int main() {
    thread t1(fun, "helloworld");
    thread t2;
    // t2 = t1;                   错误,不支持赋值重载
    // t2 = move(t1);             不报错,但不应该这么写
    t2 = thread(fun, "helloworld"); // 正确,支持移动构造和移动赋值

    t1.join();
    t2.join();
    system("pause");
    return 0;
}

常用类方法

  • join()     线程等待,一般用于主线程(如果主线程先于其他线程退出,此时其他线程的资源没人可以回收,就会报错)
  • get_id()  返回线程id,类型为thread::id 
  • joinable() 返回一个布尔值,如果该线程没有被回收返回true,否则为false
  • detach()  将本线程与主线程分离开,二者不再有联系(慎用!!!)

线程函数参数为引用时

        由于线程不支持拷贝构造与拷贝赋值,所以线程在构造时使用的是参数的拷贝,如果函数的一个参数为引用,将无法实现传递,这时就需要一个神奇的函数ref

​
void fun(int& a)
{
    ++a;
    cout << a << endl;
}

int main() {
    int a = 0;

    thread t[5];
    for (int i = 0; i < 5; i++) {
        // t[i] = thread(fun, a);  // 错误,引用无法直接传递
        t[i] = thread(fun, ref(a));     //引用需要调用 ref 

    }
    for (int i = 0; i < 5; i++) {
        t[i].join();
    }
    cout << a << endl;
    system("pause");
    return 0;
}

​

原子性操作

        多线程最主要的问题是共享数据带来的问题( 即线程安全 ) 。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦
        传统的解决方案是通过加锁保证同一时间内只允许一个线程进行修改和操作数据,不过这种方式也存在一些问题,比如当代码不是很长而加锁部分占据代码的很大一部分时,将会非常影响效率
        在c++11中引入了一种新的解决方案,即原子操作,原子操作是指不可被中断的一个或一系列操作,适当的使用原子操作会比锁的效率更高
        需包含头文件 <atomin>

对于内置类型

也可自定义类型

atmoic < T > t ;     // 声明一个类型为 T 的原子类型变量 t
注意:原子类型通常属于 " 资源型 " 数据,多个线程只能访问单个原子类型的拷贝,因此 C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator= 等,为了防止意外,标准库已经将 atmoic 模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。
#include <atomic>
int main()
{
 atomic<int> a1(0);
 //atomic<int> a2(a1);   // 编译失败
 atomic<int> a2(0);
 //a2 = a1;               // 编译失败
 return 0; }

需包含头文件 mutex

头文件mutex包含4种锁

1.std::mutex 

C++11 提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。 mutex 最常用
的三个函数:
  • lock  上锁
  • unlock 解锁
  • try_lock  尝试去锁
线程函数调用 lock() 时,可能会发生以下三种情况:
  • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁
  • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
线程函数调用 try_lock() 时,可能会发生以下三种情况:
  • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock释放互斥量
  • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

2. std::recursive_mutex
        其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权, 释放互斥量时需要调用与该锁层次深度相同次数的 unlock() ,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。
3. std::timed_mutex
std::mutex 多了两个成员函数, try_lock_for() try_lock_until()
        try_lock_for()
                接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同, try_lock 如果被调用时没有获得锁则直接返回false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false
        try_lock_until()
                接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住, 如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
4. std::recursive_timed_mutex
        相当于2,3的粘合

c++11避免死锁

锁控制不好时,可能会造成死锁,最常见的比如在锁中间代码返回,或者在锁的范围内抛异常。因此:C++11采用RAII的方式对锁进行了封装,即lock_guardunique_lock。简单点来说就是用类来封装锁,然后通过控制类的构造与析构来避免死锁问题(RAII)

lock_ground<锁的类型> lg(锁)
在lock_ground的构造处进行上锁,析构处进行解锁,从而实现加锁
缺陷,功能过少
unique_lock<锁的类型> ul(锁)
同理在构造处进行上锁,析构处进行解锁,从而实现加锁
unique_lock还提供了许多类方法来更好的管理锁
上锁 / 解锁操作 lock try_lock try_lock_for try_lock_until unlock
修改操作 :移动赋值、交换 (swap :与另一个 unique_lock 对象互换所管理的互斥量所有权) 、释放 (release :返回它所管理的互斥量对象的指针,并释放所有权 )
获取属性 owns_lock( 返回当前对象是否上了锁 ) operator bool()( owns_lock() 的功能相 同) mutex( 返回当前 unique_lock 所管理的互斥量的指针 )

http://www.kler.cn/news/308441.html

相关文章:

  • 汽车英文单词缩写汇总
  • C++学习笔记(27)
  • Rust: Warp RESTful API 如何得到客户端IP?
  • Notepad++中提升编码效率的关键快捷键
  • C++:opencv计算轮廓周长--cv::arcLength
  • 如何快速入门 Vue 3
  • MySQL基础篇(黑马程序员2022-01-18)
  • xilinx hbm ip运用
  • 自定义类型:联合和枚举
  • Java零基础-Java对象详解
  • 5. Python之数据类型
  • JVM字节码与局部变量表
  • 苹果手机铃声怎么设置自己的歌?3个方法自定义手机铃声
  • C++——多线程编程(从入门到放弃)
  • VirtualBox Install MacOS
  • 如何迈向IT行业的成功之路
  • pip install、yum install和conda install三者技术区分
  • 无人机之悬停精度篇
  • json字符串和python字典的相互转换
  • Leetcode面试经典150题-79.搜索单词
  • k8s 常见问题梳理
  • 湖北产教融合教育研究院成功协办武汉工程大学2024年同等学力申硕开学典礼
  • SpringCloud-04 OpenFeign服务调用与负载均衡
  • c语言 —— 结构变量
  • Linux基础3-基础工具4(git,冯诺依曼计算机体系结构)
  • 一步迅速了解Linux
  • 校园失物招领小程序
  • Jackson注解屏蔽某些字段读取权限
  • uniapp离线(本地)打包
  • 解读 Java 经典巨著《Effective Java》90条编程法则,第5条:优先考虑依赖注入来引用资源