线程的学习

学习目标

了解线程概念,理解线程与进程区别与联系。

掌握基本的线程控制,理解互斥和同步

了解线程分离和线程安全概念

掌握生产消费模型

掌握使用互斥量,条件变量,posix信号量

了解基于读写锁的读者写者问题

线程概念

线程(thread)是系统任务调度的基本单位,是进程内的一个独立执行流。线程在进程内部运行,本质是在进程地址空间内运行 。一个进程可以包含多个线程,每个线程都有自己的执行路径、栈空间和寄存器状态,但它们共享进程的虚拟地址空间和资源。

1、特点

1)轻量级

相对于进程(PCB)来说,线程(LWB)是轻量级的执行单位,本质上也是task_struct结构,但在CPU眼里线程更加轻量化。因此线程的创建和切换开销比进程小,线程间的通信和同步也更加高效。

2)共享资源

线程在同一个进程中共享相同的虚拟地址空间,因此它们可以直接访问进程的代码段、数据段,共享库以及共享的堆内存和文件描述符等内核资源。

也意味着多个线程可能同时访问某一资源,需要加以控制。

3)相对独立性

在多线程环境中,每个线程都有自己的独立执行路径,它们可以并发执行,相互之间的操作不会直接干扰或影响彼此。

独立的栈空间:每个线程都有自己的栈空间,用于存储局部变量、函数调用的上下文和函数参数等信息,可以并发执行不同的函数或任务。

独立的寄存器状态:每个线程有自己的寄存器状态即上下文数据,包括通用寄存器、程序计数器和其他特殊寄存器。

其他独立的资源:线程id、errno、信号屏蔽字、调度优先级等内核资源

但注意线程的独立性是需要控制的,采取适当的同步机制(如互斥锁、信号量、条件变量)来保证多个线程之间对共享资源的访问是有序和互斥的,以避免竞态条件和数据二义性问题。

2、与进程关系

1)进程的深度理解

原先理解:进程=内核结构(task_struct)+代码和数据

实际上进程是资源分配的基本单位

它是操作系统对正在执行的程序的抽象,开辟独立的虚拟地址空间、系统资源,确保系统资源的合理分配和高效利用。

2)进程与线程的区别

在Linux下进程和线程是本质是同一事物,也就是说没有真正意义上的线程,线程是用户级的进程

进程是资源分配的基本单位,线程是执行的基本单位。

一个进程可以包含多个线程,线程之间共享进程的资源,通过并发执行和共享资源来提高程序的性能和响应能力。

3、优/缺点

1)线程优点

轻量:创建、切换代价小,占用资源少

共享资源和通信:线程可以共享同一进程的资源,如内存空间、文件描述符等,简化了资源共享和通信的过程。线程之间可以通过共享内存进行快速的数据交换和通信。

并发:效率高、响应快

用途:

计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

2)线程缺点

性能损失:线程之间的频繁切换可能引入一定的开销。当线程数量增多时,而处理器较少,开销可能会成为系统的瓶颈,可能会占用大量的系统资源。这里的性能损失指的是增加了额外的同步和调度开销,而可用的处理器资源不变。

线程安全性:在多线程环境下,共享资源的访问需要进行同步和互斥操作,以避免数据竞争和保证线程安全。

编程难度提高:编写与调试一个多线程程序比单线程程序困难得多

4、线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃

线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出,健壮性或鲁棒性较低

线程控制

1、线程库的相关函数调用

1)线程创建和销毁函数
  • pthread_create():创建一个新的线程,并指定线程的执行函数和参数。

  • pthread_exit():在线程执行函数中调用,用于终止当前线程的执行并返回退出状态。区分exit()进程退出

  • pthread_detach():线程分离,不需要该线程的退出状态,如果主线程退出,其他线程也会退出

  • pthread_join():等待指定的线程终止,并获取其退出状态。

  • pthread_cancel():向指定线程发送取消请求,请求线程终止执行。

2)互斥函数
  • pthread_mutex_init():初始化互斥锁,用于保护共享资源,确保线程之间的互斥访问。

  • pthread_mutex_lock():获取互斥锁,如果锁已经被其他线程占用,则当前线程会阻塞等待。

  • pthread_mutex_unlock():释放互斥锁,允许其他线程获取该锁。

  • pthread_mutex_destroy():关闭互斥锁,锁无法再使用。

3)条件变量函数
  • pthread_cond_init():初始化条件变量,用于线程之间的条件等待和通知。

  • pthread_cond_wait():等待条件变量满足特定条件,进入等待状态,并释放相应的互斥锁。

  • pthread_cond_signal():向等待在条件变量上的线程发送信号,唤醒其中一个线程继续执行。

  • pthread_cond_broadcast():向等待在条件变量上的所有线程发送广播信号,唤醒所有等待线程。

4)信号量函数
  • sem_init():初始化一个信号量,设置初始值。

  • sem_destroy():销毁一个信号量。

  • sem_wait():执行 P 操作,将信号量的计数值减一。如果计数值变为负数,则阻塞当前线程

  • sem_post():执行 V 操作,将信号量的计数值加一。如果有其他线程在等待该信号量,则唤醒其中一个线程。

2、线程互斥

1)互斥锁

在Linux操作系统中,互斥(Mutex)是一种保护机制,主要用于保护临界区,以防止多个线程同时访问共享资源导致的数据不一致。

一条交换语句,将锁数据交互到寄存器内部,本质是将数据共享变成自己的上下文数据即变成私有的资源预定机制

互斥的主要作用如下:

  1. 资源访问控制:当多个线程需要访问共享资源(例如,全局变量,文件,数据库连接等)时,互斥锁可以保证每次只有一个线程可以访问。这确保了数据的完整性和一致性。

  2. 保证原子操作:互斥锁可以确保某一段代码(即临界区)的执行是原子的,即在一个线程执行该段代码的过程中不会被其他线程打断。这对于一些需要多个步骤同时成功或同时失败的操作非常重要。

  3. 防止死锁:虽然不恰当使用互斥锁可能会导致死锁,但如果使用得当,可以避免多线程同时请求资源导致的死锁。

  4. 线程间的同步:互斥锁也可以用于控制线程的执行顺序,确保一些需要在其他操作完成后才能进行的操作能按照预期顺序执行。

总的来说,互斥在并发编程中是一种重要的工具,它能够帮助开发者编写出正确,可预测且不含竞态条件的多线程应用程序。

2)抢票
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include "lock.hpp"
using namespace std;
int ticket =10000;
typedef struct threadData   //存储线程数据,针对于锁是局部初始化,或者其他需要向线程传递信息
{
    char name[64];  //线程name
    pthread_mutex_t *pmutex;  //互斥锁指针
}threadData;
void *start_routine(void* arg)
{
    pthread_detach();
    threadData *data=static_cast<threadData*>(arg);
    while(true)
    {
        //Lock mylock(&mymutex)基于RAII思想的锁的封装
        pthread_mutex_lock(data->pmutex);//开锁
        if(ticket>0)
        {
            usleep(1000);
            cout<<data->name<<"线程抢到票了,当前线程为:"<<data->name<<" 票的编号为:"<<ticket<<endl;
            ticket--;
            pthread_mutex_unlock(data->pmutex);//解锁
            usleep(123);
        }        
        else
        {
            cout<<"没有票了"<<endl;
            pthread_mutex_unlock(data->pmutex);  //解锁 
            break;
        }
    }
    cout<<"线程结束"<<endl;
    return nullptr;
}
int main()
{
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,nullptr);//初始化锁
    pthread_t tid1,tid2,tid3,tid4;
    threadData *d1=new threadData();
    threadData *d2=new threadData();
    threadData *d3=new threadData();
    threadData *d4=new threadData();
    strcpy(d1->name,"thread 1");
    strcpy(d2->name,"thread 2");
    strcpy(d3->name,"thread 3");
    strcpy(d4->name,"thread 4");
    d1->pmutex=&mutex;
    d2->pmutex=&mutex;
    d3->pmutex=&mutex;
    d4->pmutex=&mutex;
    pthread_create(&tid1,nullptr,start_routine,d1);
    pthread_create(&tid2,nullptr,start_routine,d2);
    pthread_create(&tid3,nullptr,start_routine,d3);
    pthread_create(&tid4,nullptr,start_routine,d4);
    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
    pthread_join(tid4,nullptr);
    pthread_mutex_destroy(&mutex);
    return 0;
}

3)基于RAII的封装
#pragma once
#include <pthread.h>
#include <iostream>
class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock,nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};
class Lock
{
public:
    Lock(Mutex* mutex)
    :_mutex(mutex)
    {
        _mutex->lock();
    }
    ~Lock()
    {
        _mutex->unlock();
    }
private:
    Mutex* _mutex;
};

4)可重入函数与线程安全

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作, 并且没有锁保护的情况下,会出现该问题。

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们 称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

常见情况:

常见的线程不安全的情况:

不保护共享变量的函数

函数状态随着被调用,状态发生变化的函数

返回指向静态变量指针的函数 调用线程不安全函数的函数

常见的线程安全的情况:

每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的

类或者接口对于线程来说都是原子操作

多个线程之间的切换不会导致该接口的执行结果存在二义性

常见不可重入的情况 :

调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的

调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构

可重入函数体内使用了静态的数据结构

常见可重入的情况:

不使用全局变量或静态变量 不使用用malloc或者new开辟出的空间

不调用不可重入函数不返回静态或全局数据,所有数据都有函数的调用者提供 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

联系:

函数是可重入的,那就是线程安全的。函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题,如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别 可重入函数是线程安全函数的一种 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生 死锁,因此是不可重入的。

5)死锁问题

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资 源而处于的一种永久等待状态。

死锁四个必要条件:

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

3、线程同步

1)多线程的问题

互斥可能导致饥饿问题:某个线程由于无法获取所需的资源或执行时间片,而无法正常执行的情况。其他线程可能长时间占用资源或高优先级线程持续抢占 CPU 时间,导致某个线程无法得到应有的资源或执行机会,从而无法继续执行。

2)条件变量

条件变量(Condition Variable)是一种线程同步机制,用于线程之间的等待和通知。条件变量允许线程在满足特定条件之前等待,并在条件满足时被其他线程通知唤醒。

若想改变状态必然涉及其他线程对共享资源的访问,条件变量通常与互斥锁(Mutex)结合使用,以实现更复杂的线程同步和协作。

条件(对应共享资源的状态,由程序员控制)、条件变量(条件满足或不满足,选择进行wait和signal)

条件变量的使用可以实现更灵活的线程同步和协作,允许线程在满足特定条件之前等待,避免了轮询的开销和饥饿锁的产生。它常用于生产者-消费者模式、线程间消息传递和资源共享等场景,能够提供高效的线程间通信和同步。然而,使用条件变量时需要注意正确的互斥锁使用和条件的精确判断,以避免竞态条件和死锁等问题。

竞态条件:时序问题导致程序异常

3)信号量

信号量(Semaphore)是一种用于线程同步和互斥的计数器。它提供了一种简单而有效的机制,用于控制对共享资源的访问,避免竞态条件和实现线程之间的互斥和协作。

信号量维护一个整型计数器和一个等待队列,可以执行以下两种操作:

  1. P 操作(等待操作):当一个线程希望访问共享资源时,它会执行 P 操作,即减少信号量的计数器。如果计数器的值变为负数,表示资源不可用,该线程将被阻塞并加入等待队列。

  2. V 操作(释放操作):当一个线程使用完共享资源后,它会执行 V 操作,即增加信号量的计数器。如果有线程在等待队列中等待资源,V 操作将唤醒一个或多个等待线程,使其继续执行。

信号量常用于以下几个场景:

  1. 互斥锁(Mutex):可以使用二值信号量(计数器为1)来实现互斥锁的功能。当计数器为0时,表示锁被占用,其他线程需要等待释放锁的信号。

  2. 限制并发访问:可以使用计数型信号量(计数器大于1)来控制对共享资源的并发访问数量。计数器的初始值决定了允许同时访问资源的线程数。

  3. 解决生产者-消费者问题:通过使用信号量来进行生产者和消费者之间的同步和协作,确保生产者和消费者之间的正确交互和资源管理。与阻塞队列相比,利用信号量的环形队列有在共享资源区并发的特点

4)生产消费模型

生产者-消费者模型是一种常见的多线程编程模型,用于解决生产者和消费者之间的协作和资源共享问题。在该模型中,生产者负责生产数据并将其放入共享缓冲区,而消费者负责从缓冲区中获取数据进行处理。

目的:解耦,提高效率

以下是生产者-消费者模型的基本原则和关键组件:

  1. 共享缓冲区(Shared Buffer):生产者和消费者之间共享的数据存储区域。它可以是一个队列、缓冲池或其他数据结构,用于存储生产者生产的数据,供消费者消费。

  2. 互斥锁(Mutex):用于保护对共享缓冲区的访问,确保在同一时间只有一个线程可以对缓冲区进行读取或写入操作,避免竞态条件。

  3. 满/空信号量(Full/Empty Semaphore):用于表示缓冲区的状态。满信号量记录缓冲区中可消费的数据数量,空信号量记录缓冲区中可生产的空闲位置数量。

  4. 生产者线程(Producer Thread):负责生成数据并将其放入共享缓冲区。当缓冲区已满时,生产者线程会等待直到有可用的空闲位置。

  5. 消费者线程(Consumer Thread):负责从共享缓冲区中获取数据并进行处理。当缓冲区为空时,消费者线程会等待直到有可消费的数据。

基本的生产者-消费者模型的流程如下:

  1. 初始化互斥锁和信号量,并设置缓冲区的初始状态。

  2. 启动生产者线程和消费者线程。

  3. 生产者线程生成数据并尝试将其放入缓冲区。如果缓冲区已满,则生产者线程等待空信号量。

  4. 消费者线程从缓冲区中获取数据并进行处理。如果缓冲区为空,则消费者线程等待满信号量。

  5. 在生产者线程放入数据后,更新满信号量和空信号量的值,以反映缓冲区的状态变化。

  6. 在消费者线程处理完数据后,同样更新满信号量和空信号量的值。

  7. 重复步骤3到步骤6,直到生产者完成所有数据的生产并退出,或消费者完成所有数据的消费并退出。

基于阻塞队列的产品模型

Task.hpp

#pragma once
​
#include <iostream>
#include <string>
​
class Task
{
public:
    Task() : elemOne_(0), elemTwo_(0), operator_('0')
    {}
    Task(int one, int two, char op) : elemOne_(one), elemTwo_(two), operator_(op)
    {}
    int operator() ()
    {
        return run();
    }
    int operator()()
    {
        int result = 0;
        switch (operator_)
        {
        case '+':
            result = elemOne_ + elemTwo_;
            break;
        case '-':
            result = elemOne_ - elemTwo_;
            break;
        case '*':
            result = elemOne_ * elemTwo_;
            break;
        case '/':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "div zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ / elemTwo_;
            }
        }
        break;
        case '%':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "mod zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ % elemTwo_;
            }
        }
        break;
        default:
            std::cout << "非法操作: " << operator_ << std::endl;
            break;
        }
        return result;
    }
    int get(int *e1, int *e2, char *op)
    {
        *e1 = elemOne_;
        *e2 = elemTwo_;
        *op = operator_;
    }
private:
    int elemOne_;
    int elemTwo_;
    char operator_;
};

BlockQueue.hpp

#pragma once
#include <queue>
#include <pthread.h>
#include<unistd.h>
#include<iostream>
using namespace std;
const uint32_t defaultCap=5;
template <class T>
class BlockQueue
{
public:
    BlockQueue(uint32_t capacity=defaultCap)
        :_capacity(cap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_proCond,nullptr);
        pthread_cond_init(&_consumerCond,nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_proCond);
        pthread_cond_destroy(&_consumerCond);
    }
    //生产
    void push(T in)
    {
        //加锁
        lock();
        //判断:满不生产,不满生产
        while(isFull())//被调用之后,需要检查条件是否已经满足。
        {
            //满---等待
            proBlockWait();
        }
        //生产
        produce(in);
        //解锁
        unlock();
        //唤醒消费者
        weakUpCon();
    }
    //消费
    T pop()
    {
        lock();
        while(isEmpty())
        {
            consumerBlockWait();
        }
        //消费
        T out=consume();
        unlock();
        weakUpPro();
        return out;
    }
private:
    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
    bool isFull()
    {
        return _cap==_bq.size();
    }
    bool isEmpty()
    {
        return _bq.size()==0;
    }
    void proBlockWait()
    {
        pthread_cond_wait(&_proCond,&_mutex);
    }
    void consumerBlockWait()
    {
        pthread_cond_wait(&_consumerCond,&_mutex);
    }
    void produce(T in)
    {
        _bq.push(in);
    }
    T consume()
    {
        T tmp=_bq.front();
        _bq.pop();
        return tmp;
    }
    void weakUpCon()
    {
        pthread_cond_signal(&_consumerCond);
    }
    void weakUpPro()
    {
        pthread_cond_signal(&_proCond);
    }
private:
    queue<T>_bq;
    uint32_t _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _proCond;
    pthread_cond_t _consumerCond;
};

BlockQueue

#include "BlockQueue.hpp"
#include "Task.hpp"
#include<time.h>
#include<cstdlib>
const string ops="+-*/%";
void* produce(void* arg)
{
    //BlockQueue<int>*pbq=static_cast<BlockQueue<int>*>(arg);
    BlockQueue<Task>*pbq=static_cast<BlockQueue<Task>*>(arg);
    while(true)
    {
        sleep(1);
        //制造数据
        //int data=rand()%10;
        int d1=rand()%10;
        int d2=rand()%10;
        char op=ops[rand()%5];
        Task t(d1,d2,op);
        //生产
        //pbq->push(data);
        pbq->push(t);
        //cout<<"生产成功,生产了:"<<data<<endl;
        cout<<"添加任务成功"<<d1<<op<<d2<<"=?"<<endl;
    }
    return nullptr;
}
void *consume(void* arg)
{
    //BlockQueue<int>*pbq=static_cast<BlockQueue<int>*>(arg);
    BlockQueue<Task>*pbq=static_cast<BlockQueue<Task>*>(arg);
    //消费
    while(true)
    {
        //sleep(1);
        //int tmp=pbq->pop();
        Task tmp=pbq->pop(); 
        int result=tmp();
        int d1,d2;
        char op;
        tmp.get(&d1,&d2,&op);
        cout<<"调用任务成功"<<d1<<op<<d2<<"="<<result<<endl;
    }
    return nullptr;
}
int main()
{
    // BlockQueue<int> bq;
    BlockQueue<Task> bq;
    srand(time(nullptr));
    pthread_t t1,t2;
    pthread_create(&t1,nullptr,produce,(void*)&bq);
    pthread_create(&t2,nullptr,consume,(void*)&bq);
    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    return 0;
}

基于环形队列的生产消费模型

RingQueue.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include<unistd.h>
#include <vector>
using namespace std;
uint32_t defaultCap=5;
template<class T>
class RingQueue
{
public:
    RingQueue(uint32_t cap=defaultCap)
        :_rq(cap)
        ,_indexData(0)
        ,_indexRoom(0)
    {
        pthread_mutex_init(&_mutexData,nullptr);
        pthread_mutex_init(&_mutexRoom,nullptr);
        sem_init(&_semData,0,0);
        sem_init(&_semRoom,0,_rq.size());
    }
    ~RingQueue()
    {
        pthread_mutex_destroy(&_mutexData);
        pthread_mutex_destroy(&_mutexRoom);
        sem_destroy(&_semData);
        sem_destroy(&_semRoom);
    }
public:
    void push(T in)
    {
        //P-room 
        sem_wait(&_semRoom);
        //加锁
        pthread_mutex_lock(&_mutexRoom);
        //放入
        _rq[_indexRoom++]=in;
        _indexRoom%=_rq.size();
        //解锁
        pthread_mutex_unlock(&_mutexRoom);
        //V-data
        sem_post(&_semData);
    }
    T pop()
    {
        sem_wait(&_semData);
        pthread_mutex_lock(&_mutexData);
        T out=_rq[_indexData++];
        _indexData%=_rq.size();
        pthread_mutex_unlock(&_mutexData);
        sem_post(&_semRoom);
        return out;
    }
private:
    vector<T> _rq;
    pthread_mutex_t _mutexData;
    pthread_mutex_t _mutexRoom;
    sem_t _semData;
    sem_t _semRoom;
    uint32_t _indexData;
    uint32_t _indexRoom;
};

RingQueue

#include "RingQueue.hpp"
#include<time.h>
#include<cstdlib>
void* produce(void* arg)
{
    RingQueue<int>*prq=static_cast<RingQueue<int>*>(arg);
    while(true)
    {
        int data=rand()%10;
        prq->push(data);
        cout<<"放入了一个数据:"<<data<<endl;
    }
    return nullptr;
}
void *consume(void* arg)
{
    RingQueue<int>*prq=static_cast<RingQueue<int>*>(arg);
    while(true)
    {
        sleep(1);
        int tmp=prq->pop();
        cout<<"拿出了一个数据:"<<tmp<<endl;
    }
    return nullptr;
}
int main()
{
    RingQueue<int> rq;
    srand(time(nullptr));
    pthread_t t1,t2;
    pthread_create(&t1,nullptr,produce,(void*)&rq);
    pthread_create(&t2,nullptr,consume,(void*)&rq);
    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    return 0;
}

4、线程池

ThreadPool.hpp

#include <pthread.h>
#include <queue>
#include <iostream>
#include "Task.hpp"
using namespace std;
uint32_t defaultNum=5;
template <class T>
class ThreadPool
{
private:
    //构造函数私有
    ThreadPool(uint32_t num=defaultNum)
        :_threadNum(num)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }
    //单例模式:懒汉,调用时实例化
    //防止拷贝
    ThreadPool(const ThreadPool<T> &)=delete;
    //防止赋值
    void operator=((const ThreadPool<T> &)=delete;
public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if(nullptr==_instance)
        {
            //基于RAII思想封装的锁
            Lock lock(&mutex);
            if(nullptr==_instance)
            {
                _instance=new ThreadPool<T>();
            }
        }
    }
    static void *threadRoutine(void *arg)
    {
        //线程分离,不需要join
        pthread_detach(pthread_self());
        //类型转化static_cast<>()
        ThreadPool<T>* ptp=static_cast<ThreadPool<T>*>(arg);
        while(true)
        {
            //任务队列加锁
            ptp->lockQueue(); 
            //任务队列是否为空
            while(ptp->isEmpty())
            {
                //为空则条件等待,并释放锁
                ptp->waitTask();
                //唤醒后则又加锁
            }
            //“消费”任务
            T tmp=ptp->pop();
            //任务队列解锁
            ptp->unlockQueue();
            int result =tmp();
            int d1,d2;
            char op;
            tmp.get(&d1,&d2,&op);
            cout<<pthread_self()<<":执行任务:"<<d1<<op<<d2<<"="<<result<<endl;
        }
    }
    void start()
    {
        //生产多线程
        for(uint32_t i=0;i<_threadNum;i++)
        {
            pthread_t t;
            pthread_create(&t,nullptr,threadRoutine,this);
        }
    }
    void push(const T& in)
    {
        //push公开,在类内实现加锁,方便使用
        lockQueue(); 
        _tq.push(in);
        choiceThread();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    T pop()
    {
        T out=_tq.front();
        _tq.pop();
        return out;
    }
    bool isEmpty()
    {
        return _tq.empty();
    }
    void lockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void waitTask()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }
    void choiceThreadForHandler()
    {
        pthread_cond_signal(&_cond);
    }
​
private:
    uint32_t _threadNum;
    queue<T> _tq;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    static ThreadPool<T> *_instance;
};
template<class T>
ThreadPool<T> * ThreadPool<T> ::_instance=nullptr;

ThreadPool.cpp

#include "ThreadPool.hpp"
#include <cstdlib>
#include <time.h>
#include <memory>
#include <unistd.h>
const string ops="+-*/%";
int main()
{
    srand(time(nullptr));
    //unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    unique_ptr<ThreadPool<Task>> tp(getInstance());
    tp->start();
    while(true)
    {
        usleep(1000);
        int d1=rand()%20;
        int d2=rand()%10;
        char op=ops[rand()%5];
        cout<< "主线程派发计算任务: " << d1<< op << d2 << "=?" << endl;
        Task t(d1,d2,op);
        tp->push(t);
    }
}

其他锁

stl、智能指针的线程安全

其他锁:乐观锁、悲观锁、自旋锁---挂起等待锁(时间短、轮询检测)

读写问题

读写锁

读者没有互斥关系:对数据没有修改

写者饥饿问题

优先级策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值