初识【线程】

一、线程

1.1 并发与并行

并发:指两个或多个事件在同一个时间段内发生。----单核CPU,并发执行
并行:指两个或多个事件在同一时刻发生(同时发生)—多核CPU,并行执行

1.2 线程与进程

进程:内存中运行的程序,每个进程都用独立的内存空间,一个应用程序有多个进程。
线程Thread:程序执行流的最小单元,线程不会执行程序,其就像载体,将要执行的代码运送到cpu进行处理。
多线程就是多个线程同时并发执行。

1.3 线程调度:

分时调度:轮流使用cpu,平均分配。
抢占式调度:先优先级高的使用,如果同级,随机选择。
a、设置线程的优先级。
b、多线程程序并不能提高程序的运行速度,但可以提高运行效率,是的cpu使用效率更高。
线程是一个进程内部的控制序列。

1.4 为什么用多线程

避免拥塞、避免CPU的空转–异步调用、提升效率,C++11提供了语言层面上的多线程,包含在头文件中。
它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。
C++11 新标准中引入了5个头文件来支持多线程编程,如下图所示:

1.5 线程与进程的区别

进程是系统进行资源分配和调用的一个独立单位
线程是进程的一个实体,是CPU调度和分派的基本单位,线程只是一段程序的执行。

二 、Pthread线程函数

2.1、创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
int pthread_create(获取线程id, 设置线程属性, 线程运行函数地址, 运行函数传参)
举例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> 
// 使用线程时需要添加<pthread.h>这个头文件
// myfunc 线程携带的函数;固定为Void*型
myfunc(void* args){
		printf("hello world\n");
	return NULL;
}
 
int main(){
    pthread_t th1;   //  声明线程th1 
    pthread_create(&th1,NULL,(void*)myfunc,NULL); //创建线程并说明需要执行的函数
    pthread_join(th1,NULL)//线程阻塞
}

案例中的main函数就是所谓的主线程,main函数结束即主线程结束,主线程结束就是进程结束,进程一旦结束所有的线程就都结束了。

创建线程时传递参数,由于创建线程可以传递一个void指针,因此只需把要传递的数据的首地址给线程入口函数即可,但在线程入口函数中需要把void指针转换成对应的类型才能使用。
在线程执行期间,线程之间共享的数据要保证有效,否可能会有段错误、脏数据。

2.2、等待线程

int pthread_join(pthread_t thread, void **retval);
Pthread_join(等待的线程id, 接受线程函数的返回值)。
注意:
1)一旦调用就会进入阻塞,会等待线程结束才返回,如果等待的线程不存在,就立即返回。
2)在等待线程结束时可以获取到线程入口函数的返回值,返回值是指针,因此需要使用二级指针来接收,当线程时要保证线程返回的指针指向的数据依然有效。
3)如果线程返回的指针指向的是堆内存,则接收到该指针后要进行释放。

2.3、获取线程自身的ID

pthread_t pthread_self(void);

2.4、比较两个线程ID

int pthread_equal(pthread_t t1, pthread_t t2);
功能:是比较两人个线程ID是不是同一个线程,原因有些系统的pthead_t是以结构体形式实现的。

2.5、线程的终止

1)、从线程的入口函数中return
2)、调用pthread_exit函数
void pthread_exit(void *retval);
功能:立即结束当前线程,如果在主线程中直接结束,相当于结束整个进程。retval:给pthread_join函数的返回值,相当于线程入口函数的return。

2.6、线程终止不可使用exit函数退出

exit功能是让进程结束,进程中的任何一个线程调用exit都会导致整个进程结束。

2.7、线程的执行轨迹

同步方式:创建线程之后,主线程可以调用pthread_join函数等待。
异步方式:创建线程之后,就和主线程没有关系,不能被等待。
默认创建出的线程是同步方式执行,可以调用pthread_detach函数使线程分离。
int pthread_detach(pthread_t thread);
int pthread_detach(被分离的线程ID);

2.8、取消线程

取消线程就是主线程或父线程向子线程发送结束请求。
int pthread_cancel(pthread_t thread);

三、C++11 thread线程函数

3.1 创建线程

第一种:
std::thread myThread ( thread_fun);//函数形式为void thread_fun()
myThread.join();
第二种:
std::thread myThread ( thread_fun(100));
myThread.join();
第三种:
std::thread (thread_fun,1).detach();//直接创建线程,没有名字
举例1:

#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    cout<<"first_sub_thread"<<endl;
}
void thread_2(int x)
{
    cout<<"x:"<<x<<endl;
  cout<<"second_sub_thread"<<endl;
}
int main()
{
  thread first ( thread_1);     	// 开启线程,调用:thread_1()
  thread second (thread_2,100);  	// 开启线程,调用:thread_2(100)
  thread third(thread_2,3);			//开启第3个线程,共享thread_2函数。
  std::cout << "main_thread\n";

  first.join(); 					//必须说明添加线程的方式            
  second.join();
  third.join();  
  std::cout << "子线程结束.\n";		//必须join完成
  return 0;
}

3.2 join与detach方式

当线程启动后,一定要在和线程相关联的thread销毁前,确定以何种方式等待线程执行结束。比如上例中的join。

detach方式:启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束主线程不会等待子线程结束。如主线程运行结束,程序则结束。
join方式:等待启动的线程完成,才会继续往下执行
join后的代码不会被执行,除非子线程结束。

可以使用joinable判断是join模式还是detach模式
if (myThread.joinable()) mythread.join();

3.3 this_thread类

有4个功能函数,具体如下:
std::this_thread::get_id() // 获取线程id
std::this_thread::yield() // 放弃线程执行,回到就绪状态
std::this_thread::sleep_for(std::chrono::seconds(1)); // 暂停1秒
std::this_thread::sleep_until(system_clock::from_time_t(mktime™)); // 直到规定分钟后执行

3.4. mutex(互斥)

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

3.5 lock与unlock

mutex常用操作:
lock():资源上锁
unlock():解锁资源
trylock():查看是否上锁,它有下列3种类情况:
(1)未上锁返回false,并锁住;
(2)其他线程已经上锁,返回true;
(3)同一个线程已经对它上锁,将会产生死锁。

3.6 lock_guard

创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。

lock_guard的特点:
创建即加锁,作用域结束自动析构并解锁,无需手工解锁
不能中途解锁,必须等作用域结束才解锁
不能复制

#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';
}

3.7 unique_lock

简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。

unique_lock的特点:
创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
可以随时加锁解锁
作用域规则同 lock_grard,析构时自动释放锁
不可复制,可移动
条件变量需要该类型的锁作为参数(此时必须使用unique_lock)
所有 lock_guard 能够做到的事情,都可以使用 unique_lock 做到,反之则不然。那么何时使lock_guard呢?很简单,需要使用锁的时候,首先考虑使用 lock_guard,因为lock_guard是最简单的锁。

#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;
}

3.8 3. 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或notify_all)通知恢复为止。
condition_variable 是一个类,这个类既有构造函数也有析构函数,使用时需要构造对应的 condition_variable 对象,调用对象相应的函数来实现上面的功能。

类型 说明
wait // Wait until notified
wait_for // Wait for timeout or until notified
wait_until // Wait until notified or time point
notify_one // 解锁一个线程,如果有多个,则未知哪个线程执行
notify_all // 解锁所有线程

四、线程锁

死锁:th1运行后,th2等待th1解锁,运行出错,th1未执行完,重新启动,th1又重新执行,th2排在th1的前面,需要等th1计算所,th1等th2结束,相互等待,形成死锁。
锁的使用方法
pthread_mutex_t lock; 声明一个锁
pthread_mutex_init(&lock,NULL); 对声明的锁进行初始化

pthread_mutex_lock(&lock); //上锁 此时其他线程就开始等待
pthread_mutex_lock(&unlock); //解锁 其他线程可以使用资源了

五、线程池

在一个程序中,如果我们需要多次使用线程,这就意味着,需要多次的创建并销毁线程。而创建并销毁线程的过程势必会消耗内存,线程过多会带来调动的开销,进而影响缓存局部性和整体性能。

因为程序边运行边创建线程是比较耗时的,所以我们通过池化的思想:在程序开始运行前创建多个线程,这样,程序在运行时,只需要从线程池中拿来用就可以了.大大提高了程序运行效率.

一般线程池都会有以下几个部分构成:

线程池管理器(ThreadPoolManager):用于创建并管理线程池,也就是线程池类
工作线程(WorkThread): 线程池中线程
任务队列task: 用于存放没有处理的任务。提供一种缓冲机制。
append:用于添加任务的接口

线程池实现代码:

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory> //unique_ptr
#include<assert.h>

const int MAX_THREADS = 1000; //最大线程数目

template <typename T>
class threadPool
{
public:
    threadPool(int number = 1);//默认开一个线程
    ~threadPool();
    std::queue<T *> tasks_queue;		   //任务队列

    bool append(T *request);//往请求队列<task_queue>中添加任务<T *>

private:
    //工作线程需要运行的函数,不断的从任务队列中取出并执行
    static void *worker(void *arg);
    void run();

private:
    std::vector<std::thread> work_threads; //工作线程

    std::mutex queue_mutex;
    std::condition_variable condition;  //必须与unique_lock配合使用
    bool stop;
};//end class

//构造函数,创建线程
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
    if (number <= 0 || number > MAX_THREADS)
        throw std::exception();
    for (int i = 0; i < number; i++)
    {
        std::cout << "created Thread num is : " << i <<std::endl;
        work_threads.emplace_back(worker, this);//添加线程--直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
    }
}
template <typename T>
inline threadPool<T>::~threadPool()
{

    std::unique_lock<std::mutex> lock(queue_mutex);
    stop = true;
    condition.notify_all();
    for (auto &ww : work_threads)
        ww.join();//可以在析构函数中join
}
//添加任务
template <typename T>
bool threadPool<T>::append(T *request)
{
    /*操作工作队列时一定要加锁,因为他被所有线程共享*/
    queue_mutex.lock();//同一个类的锁
    tasks_queue.push(request);
    queue_mutex.unlock();
    condition.notify_one(); //线程池添加进去了任务,自然要通知等待的线程
    return true;
}
//单个线程
template <typename T>
void *threadPool<T>::worker(void *arg)
{
    threadPool *pool = (threadPool *)arg;
    pool->run();//线程运行
    return pool;
}
template <typename T>
void threadPool<T>::run()
{
    while (!stop)
    {
        std::unique_lock<std::mutex> lk(this->queue_mutex);
        /* unique_lock() 出作用域会自动解锁 */
        this->condition.wait(lk, [this] { return !this->tasks_queue.empty(); });
        //如果任务为空,则wait,就停下来等待唤醒
        //需要有任务,才启动该线程,不然就休眠
        if (this->tasks_queue.empty())//任务为空,双重保障
        {
            assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。
            continue;
        }
        else
        {
            T *request = tasks_queue.front();
            tasks_queue.pop();
            if (request)//来任务了,开始执行
                request->process();
        }
    }
}
#endif


#include "mythread.h"
#include<string>
#include<math.h>
using namespace std;
class Task
{
    public:
    void process()
    {
        //cout << "run........." << endl;
        //测试任务数量
        long i=1000000;
        while(i!=0)
        {
            int j = sqrt(i);
            i--;
        }
    }
};

int main(void)
{
    threadPool<Task> pool(6);//6个线程,vector
    std::string str;
    while (1)
    {
            Task *tt = new Task();
            //使用智能指针
            pool.append(tt);//不停的添加任务,任务是队列queue,因为只有固定的线程数
            cout<<"添加的任务数量: "<<pool.tasks_queue.size()<<endl;;
            delete tt;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值