跳至主要內容

C++11多线程编程

张威大约 8 分钟c/c++多线程

C++11多线程编程

C++语言级别的多线程编程=》代码可以跨平台 windows/linux/mac

1、C++多线程介绍

thread(线程类)
mutex(互斥锁)
condition_variable(线程间的通信,条件变量)

智能锁:(自动的加锁解锁)
lock_guard
unique_lock

atomic 原子类型 基于CAS操作的原子类型 线程安全的

sleep_for(睡眠) 
C++语言层面 thread(底层用的还是下面平台的方法) 
   windows        linux  strace ./a.out(程序启动的跟踪打印的命令)
      |             |
createThread    pthread_create
  • 可以通过编译器的编译,加个宏,识别当前的操作系统来适配通过语言层面编写thread底层自动调用相应的函数。
  • ,让用户更加方便使用。

2、多线程编程

线程内容:

1、怎么创建启动一个线程?

std::thread定义一个线程对象,传入线程所需要的线程函数和参数, 线程自动开启

2、子线程如何结束

子线程函数运行完成,线程就结束了

3、主线程如何处理子线程

t.join() : 等待t线程结束,当前线程继续往下运行 t.detach() : 把t线程设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束了!

  • 测试结果:

  • join(): 主线程会等待子线程运行结束,才继续执行

  • hello thread1会永远运行在main thread之前的,因为主线程join了,在等待子线程。

子线程睡眠:

this_thread(): 是一个namespace,里面有一些方法;

  • get_id():获取线程id;
  • yield():放弃当前线程这一轮的时间片
  • sleep_for():睡眠多长时间
  • sleep_until():睡到哪一个时间点

std::chrono():定义了一些和时间相关的常量

问题:主线程运行完成,查看如果当前进程还有未运行完成的子线程,进程就会异常终止

解决办法:

  • 主线程等待子线程结束,主线程继续往下运行t1.join();

  • 把子线程设置为分离线程t1.detach();

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

void threadHandle1(int time)
{
	//让子线程睡眠time秒
	std::this_thread::sleep_for(std::chrono::seconds(time));
	cout << "hello thread1!" << endl;
}
void threadHandle2(int time)
{
	//让子线程睡眠time秒ace this_thread是namespace 
	std::this_thread::sleep_for(std::chrono::seconds(time));
	cout << "hello thread2!" << endl;
}
int main()
{
	//创建了一个线程对象,传入一个线程函数(作为线程入口函数),
	//新线程就开始运行了,没有先后顺序,随着CPU的调度算法执行 
	std::thread t1(threadHandle1, 2);
	std::thread t2(threadHandle2, 3);

	//主线程(main)运行到这里,等待子线程结束,主线程才继续往下运行
	t1.join();
	t2.join();

	//把子线程设置为分离线程,子线程和主线程就毫无关系了
	//主线程结束的时候查看其他线程
	//但是这个子线程运行完还是没运行完都和这个主线程没关系了
	//这个子线程就从这个main分离出去了
	//运行程序时也看不到这个子线程的任何输出打印了
	//t1.detach();

	cout << "main thread done!" << endl;

	//主线程运行完成,查看如果当前进程还有未运行完成的子线程
	//进程就会异常终止
	return 0;
}

3、线程间互斥—mutex互斥锁和lock_gard

所有线程都在输出,不能串行执行,因为线程都是并行执行的。

//c++ thread模拟车站三个窗口买票的程序
int ticketCount = 100;	//车站有100张车票,由三个窗口一起卖票

void sellTicket(int index) {
    while(tickeCount > 0) {
        //cout << "窗口:" << index << "卖出第:" 
        //<< ticketCount << "张票" << endl;
        cout << ticketCount << endl;
        ticketCount--;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    list<std::thread> tlist;
    for(int i = 1; i <= 3; ++i) {
        tlist.push_back(std::thread(sellTicket, i));
    }
    
    for(std::thread& t : tlist) {
        t.join();
    }
    
    cout << "所以窗口买票结束!" << endl;
	
    return 0;
}

可以看到有很多问题。(每次运行结果都存在差异,程序存在竞态条件

多线程程序 涉及 问题;

竞态条件: 多线程执行的结果不一致的情况,会随着CPU对线程不同的调用顺序,而产生不同的运行结果。

  • 不是线程安全的操作!
  • 每个线程在1个指令周期之内是要保证完成的,但是在多个指令完全由CPU的调度决定的,线程在运行完每个指令的时候,都有可能CPU的时间片到了,线程阻塞住。等待下一轮我们再轮到这个线程执行,才能把剩余的时间片给到这个线程,线程继续执行下面的指令 。 两个线程可能减完的值是相同的,然后把相同的值写回去了
  • 所以,我们要保证这个操作线程安全!!!每次只有1个线程去做减减操作

使用mutex,包含头文件:#include <mutex>

  1. #include <iostream>
    #include <thread>
    #include <mutex>
    #include <list>
    using namespace std;
    
    int ticketCount = 100;
    std::mutex mtx;	//全局的一把互斥锁
    
    //模拟买票的线程函数
    void sellTicket(int index) {
        mtx.lock();
        while(ticketCount > 0)	//特殊情况:ticketCount = 1 所以要进行 锁+双重判断
        {
            //cout << "窗口:" << index << "卖出第:" 
            //<< ticketCount << "张票" << endl;
            cout << ticketCount << endl;
            ticketCount--;
         	std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        mtx.unlock();
    }
    

上面程序还是有问题的:会导致

修改:

//模拟买票的线程函数
void sellTicket(int index) {
    while(ticketCount > 0)	//特殊情况:ticketCount = 1 所以要进行 锁+双重判断
    {
    	mtx.lock();
        //cout << "窗口:" << index << "卖出第:" 
        //<< ticketCount << "张票" << endl;
        cout << ticketCount << endl;
        ticketCount--;
     	std::this_thread::sleep_for(std::chrono::milliseconds(100));
    	mtx.unlock();
    }
}

代码还是有问题的,!!! 原因: 当ticketCount = 1时,此时线程1进入临界区执行卖票,此时ticketCount还未变为0,解决方法:

注意:这里我把休眠时间挪到解锁之后,为了cpu充分调度!

//模拟买票的线程函数
void sellTicket(int index) {
    while(ticketCount > 0)	
    {
    	mtx.lock();
        if(ticketCount > 0) {
            //cout << "窗口:" << index << "卖出第:" 
        	//<< ticketCount << "张票" << endl;
        	cout << ticketCount << endl;
        	ticketCount--;
        }
        mtx.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

问题:如果一个线程中间出现问题,或者其他情况,,怎么办?

  • 还是要采用 的思想;

  • 两者都可以

lock_guard

  • 将锁封装了,成员变量是一把互斥锁
  • 控制锁的构造和析构
  • ,类似scoped_ptr(拷贝构造和赋值函数删除掉)
  • lock_guard构造函数直接加锁;
  • lock_guard析构函数直接解锁;
//模拟买票的线程函数
void sellTicket(int index) {
    while(ticketCount > 0)	
    {
    	lock_guard<std::mutex> lock(mtx);//智能指针的思想,是栈上的局部对象,出作用域就自动析构,释放锁
        if(ticketCount > 0) {
            //cout << "窗口:" << index << "卖出第:" 
        	//<< ticketCount << "张票" << endl;
        	cout << ticketCount << endl;
        	ticketCount--;
        }
        mtx.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

unique_lock

  • 和unique_ptr类似。
  • 把带左值引用参数的拷贝构造函数和赋值重载函数删除了。
  • 支持带右值引用参数的拷贝构造函数和赋值重载函数(支持 临时对象拷贝构造一个新对象,临时对象给另一个对象赋值)。

unique_lock源码

右值引用的拷贝构造:

右值引用的赋值重载:

左值引用参数的拷贝构造函数和赋值重载函数删除

成员变量:

  • 指向一把锁的指针

lock()和unlock()方法: