c++11多线程概述

多线程前导

1.产生死锁的原因:

*系统资源不足
*进程运行推进的顺序不合适
*资源分配不当

2.产生死锁的四个必要条件

*互斥条件:一个资源每次只能被一个进程使用
*请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
*不可剥夺条件:若干进程之间形成了一种头尾相接的循环等待资源的关系

3.死锁的解除与预防

*破坏请求条件
*破坏请保持条件
*破坏不可剥夺条件
*破坏环路等待条件

多线程

c++11提供了语言层面上的多线程,包含在头文件<thread.h>中,它解决了跨平台的问题。
提供了管理线程,保护共享数据,线程同步操作,原子操作等类,c++11新标准中引入了五个头文件来支持多线程编程
多进程并发
由于操作系统对进程提供了大量的保护机制,以避免一个进程修改了另一个进程的数据,使用多进程比多线程更容易写出安全的代码。
但是这也造就了多进程并发的两个缺点:
(1.在进程间通信,无论是使用信号,套接字,还是文件,管道等方式,其使用要么比较复杂,要么就是速度较慢
(2.运行多个线程的开销很大,操作系统要分配很多的资源来对这些进程进程管理

多线程并发

多线程并发指的是在同一个进程中执行多个线程

优点

线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,但是线程不独立的拥有资源,依赖于创建它的进程而存在,
也就是说,同一个进程的多个线程 共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递。

缺点

由于缺少操作系统提供的保护机制,在多线程共享数据及通信时,就需要程序员做更多的工作 以保证对共享数据段的操作是以预想的操作顺序进行的,
并且要极力的避免死锁

1.thread

1.join方式,等待启动的线程完成,才会继续往下执行

#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    while(1)
    {
        cout<<"子线程1"<<endl;
    }
}
void thread_2(int x)
{
    while(1)
    {
        cout<<"子线程2"<<endl;
    }
}
int main()
{
    thread first ( thread_1);     // 开启线程,调用:thread_1()
    thread second (thread_2,100);  // 开启线程,调用:thread_2(100)
    first.join();                // pauses until first finishes 这个操作完了之后才能destroyed
    second.join();               // pauses until second finishes//join完了之后,才能往下执行。
    while(1)
    {
        std::cout << "主线程\n";
    }
    return 0;
}

1.2 detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束

#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    while(1)
    {
        cout<<"子线程1111"<<endl;
    }
}
void thread_2(int x)
{
    while(1)
    {
        cout<<"子线程2222"<<endl;
    }
}
int main()
{
    thread first ( thread_1);     // 开启线程,调用:thread_1()
    thread second (thread_2,100);  // 开启线程,调用:thread_2(100)

    first.detach();                
    second.detach();            
    for(int i = 0; i < 10; i++)
    {
        std::cout << "主线程\n";
    }
    return 0;
}

2.this_thread

this_thread是一个类,它有四个功能函数,具体如下:
this_thread::get_id//获取进程id
this_thread::yield()//放弃线程执行,回到就绪状态
this_thread::sleep_for(std::chrono::second(1))//暂停一秒
this_thread::sleep_until//xx时间之后执行

3.mutex

mutex头文件主要声明了与互斥量(mutex)相关的类。mutex提供了4种互斥类型,如下表所示。
mutex:最基本的mutex类
recursive_mutex:递归mutex类
time_mutex:定时mutex类
recursive_timed_mutex:递归定时mutex类

4.lock 与 unlock

lock():资源上锁
unlock():解锁资源
trylock():查看是否上锁,
(1.未上锁返回FALSE,并锁住
(2.其他线程已上锁,返回TRUE
(3.同一个线程已经对它上锁,将会产生死锁

同一个mutex变量上锁之后,一个时间段内,只允许一个线程访问它。例如

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

std::mutex mtx;           

void print_block (int n, char c) 
{  
  mtx.lock();
  for (int i=0; i<n; ++i) { std::cout << c; }
  std::cout << '\n';
  mtx.unlock();
}

int main ()
{
  std::thread th1 (print_block,50,'*');//线程1:打印*
  std::thread th2 (print_block,50,'$');//线程2:打印$

  th1.join();
  th2.join();

  return 0;
}

如果是不同mutex变量,因为不涉及到同一资源的竞争,所以下列代码运行可能会出现交替打印的情况,或者另一个线程可以修改共同的全局变量!!!:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

std::mutex mtx_1;           // mutex for critical section
std::mutex mtx_2;           // mutex for critical section

int test_num = 1;

void print_block_1 (int n, char c) {
  mtx_1.lock();
  for (int i=0; i<n; ++i) 
  {
      test_num = 1;
      std::cout<<test_num<<std::endl;
  }
  std::cout << '\n';
  mtx_1.unlock();
}
void print_block_2 (int n, char c) {
  mtx_2.lock();
  test_num = 2;
  for (int i=0; i<n; ++i) 
  {
      test_num = 2;
      std::cout<<test_num<<std::endl;
  }
  mtx_2.unlock();
}

int main ()
{
  std::thread th1 (print_block_1,10000,'*');
  std::thread th2 (print_block_2,10000,'$');

  th1.join();
  th2.join();

  return 0;
}

5.lock_guard

创建lock_guard对象时,它将尝试获取提供它的互斥锁的所有权,当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量
lock_guard的特点:
(1.创建即加锁,作用域结束自动析构并解锁,无需手工解锁
(2.不能中途解锁,必须等作用域结束才解锁
(3.不能复制

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

int g_i = 0;
std::mutex g_i_mutex;  // protects g_i,用来保护g_i

void safe_increment()
{
    const std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
    // g_i_mutex自动解锁
}

int main()
{
	std::cout << "main id: " <<std::this_thread::get_id()<<std::endl;
    std::cout << "main: " << g_i << '\n';

    std::thread t1(safe_increment);
    std::thread t2(safe_increment);

    t1.join();
    t2.join();

    std::cout << "main: " << g_i << '\n';
}

说明;
(1.该程序的功能为,每经过一个线程,g_i加1;
(2.因为涉及到的共同资源g_i,所以需要一个共同的mutex:g_i_mutex
(3.main线程的id为1,所以下次的线程id依次加1

6.unique_lock

简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,
同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。
unique_lock的特点:
(1.创建时可以不锁定(通过指定第二个参数为std::defer_lock,而在需要时再锁定
(2.可以随时加锁解锁
(3.作用域规则同lock_guard,析构时自动释放
(4.不可复制,不可移动
(5.条件变量需要该类型的锁作为参数(此时必须使用unique_lock)

#include <mutex>
#include <thread>
#include <iostream>
struct Box {
    explicit Box(int num) : num_things{num} {}

    int num_things;
    std::mutex m;
};

void transfer(Box &from, Box &to, int num)
{
    // defer_lock表示暂时unlock,默认自动加锁
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);

    //两个同时加锁
    std::lock(lock1, lock2);//或者使用lock1.lock()

    from.num_things -= num;
    to.num_things += num;
    //作用域结束自动解锁,也可以使用lock1.unlock()手动解锁
}

int main()
{
    Box acc1(100);
    Box acc2(50);

    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

    t1.join();
    t2.join();
    std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
    std::cout << "acc2 num_things: " << acc2.num_things << std::endl;
}

说明:
(1.该函数的作用是,从一个结构体中的变量减去一个num,加载到另一个结构体的变量中去
(2.std::mutex m 在结构体中,mutex不是共享的,但是只需要一把锁也能锁住,因为引用传递后,同一把锁传给了两个函数
(3.cout需要在join后面进行,要不然cout的结果不一定是最终算出来的结果
(4.ref用于包装按引用传递的值
(5.cref用于包装按const传递的值

7.condition_variable

condition_variable的头文件有两个variable类,一个是condition_variable,另一个是condition_variable_any.
condition_variable必须结合unique_lock使用,condition_variable_any可以使用任何的锁,下面以condition_variable为例进行介绍
condition_variable变量可以阻塞(wait,wait_for,wait_until)调用的线程直到使用(notify_one或notigy)_all)通知恢复为止。
condition_variable是一个类,这个类既有构造函数也有析构函数,使用时要构造对应的condition_variable对象,调用对象相应的函数来实现上面的功能

类型说明
condition_variable构建对象
析构删除
waitwait until notified
wait_forWait for timeout or until notified
wait_untilWait until notified or time point
notify_one解锁一个线程,如果有多个,则未知哪个线程执行
notify_all解锁所有线程
cv_status这是一个类,表示variable 的状态,如下所示
enum class cv_status { no_timeout, timeout }

7.1.wait

当前线程调用 wait() 后将被阻塞
在线程被阻塞时,该函数会自动调用 unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait()函数也是自动调用 lock(),使得对象的状态和 wait 函数被调用时相同。

#include <iostream>           // std::cout
#include <thread>             // std::thread, std::this_thread::yield
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;
bool shipment_available() {return cargo!=0;}

void consume (int n) {
    for (int i=0; i<n; ++i) {
        std::unique_lock<std::mutex> lck(mtx);//自动上锁
        //第二个参数为false才阻塞(wait),阻塞完即unlock,给其它线程资源
        cv.wait(lck,shipment_available);
        // consume:
        std::cout << cargo << '\n';
        cargo=0;
    }
}

int main ()
{
    std::thread consumer_thread (consume,10);

    for (int i=0; i<10; ++i) {
        //每次cargo每次为0才运行。
        while (shipment_available()) std::this_thread::yield();
        std::unique_lock<std::mutex> lck(mtx);
        cargo = i+1;
        cv.notify_one();
    }

    consumer_thread.join();,
    return 0;
}

说明:
(1.主线程中的while,每次在cargo=0才运行
(2.每次cargo被置为0,会通知子线程unblock(非阻塞),也就是子线程可以继续往下执行
(3.子线程中cargo被置为0后,wait又一次启动等待,也就是说shipment_available为FALSE,则等待。

7.2.wait_for

与std::condition_variable::wait() 类似,不过 wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。 而一旦超时或者收到了其他线程的通知,wait_for返回,剩下的处理步骤和 wait()类似。

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>             // std::chrono::seconds
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status

std::condition_variable cv;

int value;

void read_value() {
  std::cin >> value;
  cv.notify_one();
}

int main ()
{
  std::cout << "Please, enter an integer (I'll be printing dots): \n";
  std::thread th (read_value);

  std::mutex mtx;
  std::unique_lock<std::mutex> lck(mtx);
  while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) {
    std::cout << '.' << std::endl;
  }
  std::cout << "You entered: " << value << '\n';

  th.join();

  return 0;
}

说明:
通知或者超时都会解锁,所以主线程会一直打印。
事例中只要过去一秒,就会不断的打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值