c++ 多线程学习入门篇精讲(1)

目录

基本概念

C++中的多线程

示例1:创建线程并执行函数 

示例2:传递参数给线程函数

示例3:使用Lambda表达式

 线程同步

什么是线程同步?

(1)数据竞争(Data Race)的例子

(2)不一致状态(Inconsistent State)的例子

数据竞争和不一致状态的区别

 尝试手动加锁解决问题

手动实现锁的问题

C++ 中的线程同步机制

1. 互斥锁(std::mutex)

示例代码:使用std::mutex进行加锁解锁

 2. 锁管理器(std::lock_guard 和 std::unique_lock)

lock_guard示例代码

 lock_guard 的构造函数

 unique_lock示例代码

1. unique_lock的默认构造函数

2. 关联互斥锁并立即加锁

3.关联互斥锁但不立即加锁

5.关联互斥锁并尝试加锁

6.关联互斥锁并假设已加锁

 unique_lock灵活性展示

1.延迟加锁

2.尝试加锁

3.与条件变量配合使用

4.unique_lock 的所有权转移

unique_lock 与 lock_guard 的区别

锁管理器总结

(补充)关于emplace_back与 push_back 的区别

(补充)关于基于范围的for循环

(补充)关于auto关键字

(补充)为什么使用 auto&?


基本概念

  • 线程:线程是程序执行的最小单位,一个进程可以包含多个线程

  • 并发:多个线程同时执行,但不一定是真正的同时(取决于CPU核心数)。

  • 并行:多个线程真正同时执行(需要多核CPU)。

C++中的多线程

C++11引入了std::thread类,用于创建和管理线程。你可以通过传递一个函数或可调用对象来创建一个线程。

示例1:创建线程并执行函数 

#include <iostream>
#include <thread>
using namespace std; 
void hello() {
    cout << "Hello from thread!" << endl;
}

int main() {
	//创建一个线程t,并执行hello函数。
    thread t(hello);  
    //主线程main等待t线程执行完毕。
    //如果不调用join(),主线程可能会在子线程完成之前结束,导致未定义行为
    t.join(); // 等待线程结束
    cout << "Hello from main!" << endl;
    return 0;
}

示例2:传递参数给线程函数

#include <iostream>
#include<string>
#include <thread>
using namespace std; 
void hello(string str) {
    cout << str << endl;
}

int main() {
	string s="hello form s!"; 
    thread t(hello,s);  
    t.join(); // 等待线程结束
    cout << "Hello from main!" << endl;
    return 0;
}

示例3:使用Lambda表达式

#include <iostream>
#include <thread>
using namespace std; 

int main() {
	//使用Lambda表达式作为线程函数,简洁且方便。
    thread t([](){
    	cout<<"hello from lambda thread!"<<endl;
	}); 
    t.join(); // 等待线程结束
    cout << "Hello from main!" << endl;
    return 0;
}

 线程同步

线程同步是多线程编程中非常重要的概念,尤其是在多个线程需要共享资源(如变量、数据结构等)时。如果没有正确的同步机制,可能会导致数据竞争(Data Race)或未定义行为(Undefined Behavior)

什么是线程同步?

线程同步是指通过某种机制,确保多个线程在访问共享资源时能够有序、安全地进行操作,避免数据竞争和不一致的问题。

为什么需要线程同步?

在多线程环境中,多个线程可能同时访问和修改共享资源。如果没有同步机制,可能会导致以下问题:

  • 数据竞争:多个线程同时修改同一个变量,导致结果不可预测。

  • 不一致状态:一个线程正在修改数据,而另一个线程读取了未完全更新的数据,导致逻辑错误。

(1)数据竞争(Data Race)的例子

  • 定义:多个线程同时访问共享资源(至少有一个线程是写操作),且没有正确的同步机制。

  • 关注点内存访问的冲突,即多个线程同时读写同一块内存。

  • 表现:程序行为不可预测,可能导致崩溃、数据损坏或结果错误。

示例代码:

#include <iostream>
#include <thread>
using namespace std;
int shared_value = 0; // 共享变量

void increment(char name) {
	//多个线程同时修改 shared_value
    for (int i = 0; i < 100000; ++i) {
        shared_value++;  
        if(i==99999) cout<<"over\n"; 
    }
}

int main() {
	thread a(increment,'a');
	thread b(increment,'b');
	a.join();
	b.join();
    cout << "Final value: " << shared_value << endl;
    return 0;
}

结果:

        一定输出两个over, 但是Final value每次运行的结果可能不同,例如 :

        Final value: 166278   

        Final value: 132402

问题分析:

首先,两个线程都输出了over,说明它们确确实实地执行了方法,方法中完成了100000次循环。

那么问题就只能出现在shared_value的自增操作上。

  • shared_value++ 并不是一个原子操作,它分为以下几步:

    1. 从内存中读取 shared_value 的值。

    2. 将值加 1。

    3. 将结果写回内存。

  • 由于没有同步机制,多个线程可能同时读取和修改 shared_value,可能会导致其中一个线程的修改被覆盖。

  • 表现:最终结果可能小于预期值(200000),且每次运行结果可能不同。

(2)不一致状态(Inconsistent State)的例子

  • 定义:多个线程同时访问共享资源,导致共享资源处于逻辑上不一致的状态。

  • 关注点逻辑正确性,即共享资源的语义是否被破坏。

  • 表现:共享资源的逻辑状态不符合预期,但程序可能不会崩溃。

示例代码:

#include <iostream>
#include <thread>
using namespace std;
struct BankAccount {//银行账户结构体 
    int balance = 0;
    void deposit(int amount) {
        balance += amount;  // 存款
    }
    void withdraw(int amount) {
        balance -= amount;  // 取款
    }
};
BankAccount account;  // 共享的银行账户

void deposit_money() {
    for (int i = 0; i < 100000; ++i) {
        account.deposit(1);  // 每次存 1 元
    	if(i==99999) cout<<"存款完毕\n";
    }
}

void withdraw_money() {
    for (int i = 0; i < 100000; ++i) {
        account.withdraw(1);  // 每次取 1 元
        if(i==99999) cout<<"取款完毕\n";
    }
}

int main() {
    thread t1(deposit_money);
    thread t2(withdraw_money);
    t1.join();
    t2.join();

    cout <<"Final balance: "<<account.balance <<endl;
    return 0;
}

 结果:

        一定会输出存款完毕和取款完毕,但是Final balance每次运行的结果都不同,比如:

        Final balance: 29467

        Final balance: -38703

问题分析

  • balance += amount 和 balance -= amount 都不是原子操作。

  • 如果两个线程同时修改 balance,可能会导致以下情况:

    1. 线程 A 读取 balance 的值为 100。

    2. 线程 B 读取 balance 的值也为 100。

    3. 线程 A 将 balance 更新为 101。

    4. 线程 B 将 balance 更新为 99。

  • 表现:最终 balance 的值可能是错误的,甚至出现负数(如 -123),但程序不会崩溃。

数据竞争和不一致状态的区别

<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值