【linux】多线程

一、linux中线程该如何理解

线程:是进程内的一个执行分支。

linux没有真正意义上的线程,而是用进程的内核数据结构模拟的线程(复用进程的数据结构和管理算法)。

linux实现方案:

1)在linux中,线程在进程"内部"执行,线程在进程的地址空间内运行,因为任何执行流要执行,都要有资源,而地址空间是进程的资源窗口。

2)在linux中,线程的执行粒度要比进程更细,线程执行进程代码的一部分。

如何理解我们以前的进程?

操作系统以进程为单位,给我们分配资源,我们当前的进程内部,只有一个执行流。

二、重新定义线程和进程

什么叫做线程?

我们认为,线程是操作系统调度的基本单位。

重新理解进程?

内核观点,进程是承担分配系统资源的基本实体。线程是进程内部的执行流资源(执行流也是资源)。

进程 = 多个内核数据结构(task_struct) + 代码和数据。

三、再谈地址空间

线程目前分配资源,本质就是分配地址空间的范围。

虚拟地址是如何转换到物理地址的(虚拟地址是32位的)?

找到了物理地址,如何知道取几个字节呢?

起始地址 + 类型(int/double/char等) = 起始地址 + 偏移量 --------> x86CPU的特点!

四、linux线程周边的概念

1、为什么线程比进程更轻量化?

整个生命周期中

1)创建和释放更加轻量化(生死)。

2)切换更加轻量化(运行)。

线程内的切换不需要重新加载cache数据,CPU的cache中有缓存的热数据。

2、线程的优点
1)创建一个新线程的代价要比创建一个新进程小得多。
2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3)线程占用的资源要比进程少很多。
4)能充分利用多处理器的可并行数量。
5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
7)I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
3、线程的缺点
1)性能损失:
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
2)健壮性降低:
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3)缺乏访问控制:
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4)编程难度提高:
编写与调试一个多线程程序比单线程程序困难得多。
4、线程异常
1)单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
2)线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
5、线程用途
1)合理的使用多线程,能提高CPU密集型程序的执行效率。
2)合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。
五、linux进程VS线程
进程是资源分配的基本单位。
线程是调度的基本单位。
线程共享进程数据,但也拥有自己的一部分数据:
线程ID
一组寄存器

errno
信号屏蔽字
调度优先级
进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
六、linux线程控制
内核中没有很明确的线程概念,只有轻量级进程的概念,所以不会给我们直接提供线程的系统调用,只会给我们提供轻量级进程的系统调用。但是我们用户需要线程的接口,所以linux程序员对轻量级进程接口进行封装,为用户在应用层提供了pthread线程库,直接提供了线程的接口。几乎所有的linux平台,都是默认自带这个库的。linux中编写多线程代码需要使用第三方pthread库!
线程接口的使用:
1) pthread_create(创建线程)


参数:
thread:输出型参数,返回线程ID
attr:设置线程的属性,一般设为nullptr
start_routine:是个函数指针,线程启动后要执行的函数
arg:创建线程成功,新线程回调线程函数的时候,需要参数,这个参数就是给线程函数传递的
返回值:成功返回0,失败返回错误码
代码示例:

复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

//new thread
void *threadRoutine(void *args)
{
    while(true)
    {
        cout << "new thread, pid: " << getpid() << endl;
        sleep(2);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);//不是系统调用

    while(true)
    {
        cout << "main thread, pid: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

运行结果:

LWP: light weight process (轻量级进程)
主线程的PID = LWP,新线程的PID != LWP。

2)pthread_join(线程等待)
为什么需要线程等待?
已经退出的线程,其空间没有释放,造成内存泄漏。


参数:
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0,失败返回错误码
代码示例:

复制代码
#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

//new thread
void *threadRoutine(void *args)
{
    int cnt = 5;
    while(true)
    {
        cout << "new thread, pid: " << getpid() << endl;
        sleep(2);
        cnt--;
        if(cnt == 0) break;
    }
    return (void*)100;
}

int main()
{
    PTHREAD_CANCELED;
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);//不是系统调用
    sleep(1);
    pthread_cancel(tid);//线程被取消,retval会被设置为特殊值 PTHREAD_CANCELED(-1)
    void *retval;
    pthread_join(tid, &retval);
    cout << "retval: " << (long long int)retval << endl;
    return 0;
}


运行结果:

3)pthread_self

返回值:返回线程自己的id
代码示例:

复制代码
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>

using namespace std;

string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);
    return hex;
}

void *threadRoutine(void *args)
{
    while(true)
    {
        // pthread_self()返回线程自己的id
        cout << "thread id : " << toHex(pthread_self()) << endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");
    sleep(1);
    cout << "main thread create thread done, new thread id : " << toHex(tid) << endl;
    pthread_join(tid, nullptr);
    return 0;
}

运行结果:

线程库要维护线程概念,不用维护线程的执行流,线程库注定了要维护多个线程属性集合,线程库要管理这些线程-----> 先描述,再组织。
我们用的原生线程库,要加载到内存中。

这是轻量级进程的系统调用接口,pthread线程库封装了它。
除了主线程,所有其他线程的独立栈,都在共享区,具体来讲是在pthread库中,tid指向的用户tcb中。每一个线程的库级别的tcb的起始地址,叫做线程的tid。
linux线程是用户级线程,用户级执行流 :内核LWP = 1 :1。
每一个线程都会有自己独立的栈结构。但是线程和线程之间,几乎没有秘密。线程的栈上的数据,也是可以被其他线程看到并访问的。
全局变量是被所有的线程同时看到并访问的。

如果线程想要一个私有的全局变量呢?

复制代码
__thread int g_val = 100;//线程的局部存储!只能定义内置类型,不能用来修饰自定义类型!
//__thread是编译选项(前面是两个_)

终止某个线程而不终止整个进程的方法:
1)从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2)线程可以调用pthread_ exit终止自己。

3) 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

七、分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

复制代码
int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。

复制代码
pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
八、linux线程互斥
1、用多线程模拟一轮抢票
代码示例:

复制代码
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <stdio.h>
#include <cstring>
using namespace std;

#define NUM 4

class threadData
{
public:
    threadData(int number)
    {
        threadname = "thread-" + to_string(number);
    }
public:
    string threadname;
};

int tickets = 1000;

void *getTicket(void *args)
{
    threadData *td = static_cast<threadData*>(args);
    const char *name = td->threadname.c_str();
    while(true)
    {
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get s ticket: %d\n", name, tickets);//共享数据---数据不一致
            tickets--;//对一个全局变量进行多线程并发--/++操作安全吗?
            //计算的时候,要重新读取数据!
        }
        else
        break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{
    vector<pthread_t> tids;
    vector<threadData*> thread_datas;
    for(int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData(i);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);
        tids.push_back(tid);
    }

    for(auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }

    for(auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}

运行结果:

ticket--:
1)先将tickets读入到CPU的寄存器中
2)CPU内部进行--操作
3)将计算结果写回内存
每一步都会对应一条汇编操作:
1)mov [XXX] eax
2) --
3) mov eax [XXX]

寄存器不等于寄存器的内容。
线程在执行的时候,将共享数据加载到CPU寄存器的本质:
把数据的内容,变成了自己的上下文,以拷贝的方式给自己单独拿了一份。
怎么解决数据不一致问题?
对共享数据的任何访问,保证任何时候都只有一个执行流访问 ------ > 互斥 -----> 锁
2、线程互斥的相关概念
临界资源:多线程执行流共享的资源就叫做临界资源。
临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。
加锁的本质:用时间来换取安全。
加锁的表现:线程对于临界区代码串行执行。
加锁原则:尽量保证临界区代码越少越好。
锁本身就是共享资源,所以申请锁和释放锁本身就被设计成为了原子性操作。
纯互斥环境,如果锁分配不够合理,容易导致其他线程的饥饿问题,但是不是说只要有互斥,必有饥饿。适合纯互斥的场景,就用互斥。
按照一定的顺序性获取资源称为同步。
在临界区中,线程可以被切换,在线程被切出去时,是持有锁被切走的,其他线程是无法进入临界区访问临界资源的。
对于其他线程来说,一个线程要么没有锁,要么释放锁。当前线程访问临界区的过程,对于其他线程来说是原子的。
代码示例:

复制代码
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <stdio.h>
#include <cstring>
using namespace std;

#define NUM 4

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//静态分配,无需销毁

class threadData
{
public:
    threadData(int number/*, pthread_mutex_t *mutex*/)
    {
        threadname = "thread-" + to_string(number);
        //lock = mutex;
    }
public:
    string threadname;
    //pthread_mutex_t *lock;
};

int tickets = 1000;

void *getTicket(void *args)
{
    threadData *td = static_cast<threadData*>(args);
    const char *name = td->threadname.c_str();
    while(true)
    {
        //pthread_mutex_lock(td->lock);//申请锁成功,才能往后执行,不成功,阻塞等待
        pthread_mutex_lock(&lock);//加锁
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get s ticket: %d\n", name, tickets);
            tickets--;
            //pthread_mutex_unlock(td->lock);
            pthread_mutex_unlock(&lock);
        }
        else
        {
            //pthread_mutex_unlock(td->lock);
            pthread_mutex_unlock(&lock);//解锁
            break;
        }
        usleep(13);//模拟抢到票的后续动作
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{
    //pthread_mutex_init(lock, nullptr);//动态分配,需要销毁
    vector<pthread_t> tids;
    vector<threadData*> thread_datas;
    for(int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData(i/*, lock*/);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);
        tids.push_back(tid);
    }

    for(auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }

    for(auto td : thread_datas)
    {
        delete td;
    }

    //pthread_mutex_destroy(lock);//销毁互斥量
    return 0;
}

运行结果:


3、锁的原理
tickets--不是原子的,会变成3条汇编语句。
原子:一条汇编语句就是原子的。

交换的本质:把内存中的数据,交换到CPU的寄存器中。
线程被切换时,把数据交换到线程的硬件上下文中(线程私有的),相当于把一个共享的锁,让一个线程以一条汇编的方式,交换到自己的上下文中,也就是当前线程持有锁了。
4、锁的应用----封装
代码示例:

复制代码
#pragma once

#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):lock_(lock)
    {}
    void Lock()
    {
        pthread_mutex_lock(lock_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(lock_);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *lock_;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock):mutex_(lock)
    {
        mutex_.Lock();
    }
    ~LockGuard()
    {
        mutex_.Unlock();
    }
private:
    Mutex mutex_;
};

void *getTicket(void *args)
{
    threadData *td = static_cast<threadData*>(args);
    const char *name = td->threadname.c_str();
    while(true)
    {
        {
            LockGuard lockguard(&lock);//临时的LockGuard对象,RAII风格的锁!
            if(tickets > 0)
            {
                usleep(1000);
                printf("who=%s, get s ticket: %d\n", name, tickets);
                tickets--;
            }
            else
                break;
        }
        usleep(13);//模拟抢到票的后续动作
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

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

死锁四个必要条件(必须同时满足):
1)互斥条件:一个资源每次只能被一个执行流使用。
2)请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
3)不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
4)循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。
避免死锁:
1)破坏死锁的四个必要条件
2)加锁顺序一致
3)避免锁未释放的场景
4)资源一次性分配
避免死锁算法:
死锁检测算法
银行家算法
九、linux线程同步
1、什么是同步?
同步问题是保证数据安全的情况下,让我们的线程访问资源具有一定的顺序性。
2、条件变量
条件变量必须依赖于锁的使用。
条件变量函数初始化

复制代码
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

参数:
cond:要初始化的条件变量
attr:NULL

销毁

复制代码
int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

复制代码
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量

唤醒等待

复制代码
int pthread_cond_broadcast(pthread_cond_t *cond);//全部唤醒
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个

代码示例:

复制代码
#include <iostream>
#include <unistd.h>
#include <pthread.h>

int cnt = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化一个互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化一个条件变量

void *Count(void *args)
{
    pthread_detach(pthread_self());
    uint64_t number = (uint64_t)args;
    std::cout << "pthread: " << number << " create success" << std::endl;

    while(true)
    {
        pthread_mutex_lock(&mutex);
        //如何知道要让一个线程去休眠?临界资源不就绪,临界资源也是有状态的!
        //如何知道临界资源是否就绪?判断!判断就是访问临界资源,也就是判断必须在加锁之后!
        pthread_cond_wait(&cond, &mutex);//未就绪,让当前线程等待时,会自动释放锁!
        //不管临界资源的状态情况
        std::cout << "pthread: " << number << " , cnt: " << cnt++ << std::endl;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    for(uint64_t i = 0; i < 5; i++)
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, Count, (void*)i);
        usleep(1000);
    }
    sleep(3);
    std::cout << "main thread ctrl begin: " << std::endl;

    while(true)
    {
        sleep(1);
        //pthread_cond_signal(&cond);//唤醒在cond的等待队列的一个线程,默认都是第一个
        pthread_cond_broadcast(&cond);//全部唤醒
        std::cout << "signal on thread..." << std::endl;
    }

    return 0;
}

3、CP问题---consumer producter


3种关系:
生产者vs生产者:互斥
消费者vs消费者:互斥
生产者vs消费者:互斥,同步
2种角色:
生产者和消费者
1个交易场所:
特定结构的内存空间
优点:
1)支持忙闲不均
2)生产和消费进行解耦
生产者的数据从哪里来?
用户、网络等。
生产者线程:
生产者生产的数据也是要花时间获取的。
1)获取数据
2)生产数据到队列
消费者线程:
消费者做数据加工处理也要花时间。
1)消费数据
2)加工处理数据
总的来看,生产和消费是高效的。
4、实现CP
1) main.cc

复制代码
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <ctime>

void *Consumer(void *args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);

    while(true)
    {
        //消费
        Task t = bq->pop();

        //计算
        //t.run();
        t();

        std::cout << "处理任务: " << t.GetTask() << "运算结果是:" << t.GetResult() << 
        " thread id: " << pthread_self() << std::endl; 
    }
}

void *Productor(void *args)
{
    int len = opers.size();
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while(true)
    {
        //模拟生产者生产数据
        int data1 = rand() % 10 + 1;//[1, 10]
        usleep(10);
        int data2 = rand() % 10;
        char op = opers[rand() % len];
        Task t(data1, data2, op);

        //生产
        bq->push(t);
        std::cout << "生产了一个任务:" << t.GetTask() << " thread id: " << pthread_self()
        << std::endl;
        sleep(1);
    }
}

int main()
{
    srand(time(nullptr));
    BlockQueue<Task> *bq = new BlockQueue<Task>();
    pthread_t c[3], p[5];
    for(int i = 0; i < 3; i++)
    {
        pthread_create(c + i, nullptr, Consumer, bq);
    }

    for(int i = 0; i < 5; i++)
    {
        pthread_create(p + i, nullptr, Productor, bq);
    }

    for(int i = 0; i < 3; i++)
    {
        pthread_join(c[i], nullptr);
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(p[i], nullptr);
    }
    delete bq;
    return 0;
}

2) BlockQueue.hpp

复制代码
#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

template <class T>
class BlockQueue
{
    static const int defalutnum = 20;
public:
    BlockQueue(int maxcap = defalutnum):maxcap_(maxcap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&c_cond_, nullptr);
        pthread_cond_init(&p_cond_, nullptr);
        //low_water_ = maxcap_ / 3;
        //high_water_ = (maxcap_ * 2) / 3;
    }

    T pop()
    {
        pthread_mutex_lock(&mutex_);
        while(q_.size() == 0)//判断资源是否就绪,是通过在临界资源内部判断的。
        {
            pthread_cond_wait(&c_cond_, &mutex_);
            //当前是持有锁的,调用会自动释放锁,当因唤醒而返回时,重新持有锁。
        }
        
        T out = q_.front();
        q_.pop();

        //if(q_.size() < low_water_) pthread_cond_signal(&p_cond_);
        pthread_cond_signal(&p_cond_);
        pthread_mutex_unlock(&mutex_);

        return out;
    }

    void push(const T &in)
    {
        pthread_mutex_lock(&mutex_);
        while(q_.size() == maxcap_)//防止线程被伪唤醒
        {   
            pthread_cond_wait(&p_cond_, &mutex_);
        }
        //1.队列没满 2.被唤醒
        q_.push(in); 
        //if(q_.size() > high_water_) pthread_cond_signal(&c_cond_);
        pthread_cond_signal(&c_cond_);
        pthread_mutex_unlock(&mutex_);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }
private:
    std::queue<T> q_;//共享资源,q被当作整体使用,q只有一份,加锁。
    //int mincap_;
    int maxcap_;//极值
    pthread_mutex_t mutex_;
    pthread_cond_t c_cond_;
    pthread_cond_t p_cond_;

    //int low_water_;
    //int high_water_;
};

3) Task.hpp

复制代码
#pragma once
#include <iostream>
#include <string>

std::string opers = "+-*/%";

enum
{
    DivZero = 1,
    ModZero,
    Unknown
};

class Task
{
public:
    Task(int x, int y, char op) 
    : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0)
    {}
    void run()
    {
        switch(oper_)
        {
        case '+':
            result_ = data1_ + data2_;
            break;
        case '-':
            result_ = data1_ - data2_;
            break;
        case '*':
            result_ = data1_ * data2_;
            break;
        case '/':
            {
                if(data2_ == 0) exitcode_ = DivZero;
                else result_ = data1_ / data2_;
            }
            break;
        case '%':
            {
                if(data2_ == 0) exitcode_ = ModZero;
                else result_ = data1_ % data2_;
            }
            break;
        default:
            exitcode_ = Unknown;
            break;
        }
    }
    void operator()()
    {
        run();
    }
    std::string GetResult()
    {
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r += "=";
        r += std::to_string(result_);
        r += "[code: ";
        r += std::to_string(exitcode_);
        r += "]";

        return r;
    }
    std::string GetTask()
    {
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r += "=?";
        return r;
    }
    ~Task()
    {}
private:
    int data1_;
    int data2_;
    char oper_;

    int result_;
    int exitcode_;
};

十、信号量

1、信号量的本质

信号量的本质是一把计数器,这把计数器的本质是用来描述资源数目的,把资源是否就绪放在了临界区之外,申请信号量时,其实就已经间接的在做判断了。

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

RingQueue.hpp:

复制代码
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>

const static int defaultcap = 5;

template<class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }
    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }
public:
    RingQueue(int cap = defaultcap)
    :ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0)
    {
        sem_init(&cdata_sem_, 0, 0);
        sem_init(&pspace_sem_, 0, cap);

        pthread_mutex_init(&c_mutex_, nullptr);
        pthread_mutex_init(&p_mutex_, nullptr);
    }
    void Push(const T &in)//生产
    {
        P(pspace_sem_);

        Lock(p_mutex_);
        ringqueue_[p_step_] = in;
        p_step_++;
        p_step_ %= cap_;
        Unlock(p_mutex_);

        V(cdata_sem_);
    }
    void Pop(T *out)//消费
    {
        P(cdata_sem_);

        Lock(c_mutex_);
        *out = ringqueue_[c_step_];
        c_step_++;
        c_step_ %= cap_;
        Unlock(c_mutex_);

        V(pspace_sem_);
    }
    ~RingQueue()
    {
        sem_destroy(&cdata_sem_);
        sem_destroy(&pspace_sem_);

        pthread_mutex_destroy(&c_mutex_);
        pthread_mutex_destroy(&p_mutex_);
    }
private:
    std::vector<T> ringqueue_;
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem_;//消费者关注的数据资源
    sem_t pspace_sem_;//生产者关注的空间资源

    pthread_mutex_t c_mutex_;
    pthread_mutex_t p_mutex_;

};

Main.cpp:

复制代码
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include "RingQueue.hpp"
#include "Task.hpp"

using namespace std;

struct ThreadData
{
    RingQueue<Task> *rq;
    string threadname;
};

void *Productor(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    string name = td->threadname;
    int len = opers.size();
    while(true)
    {
        //1.获取数据
        int data1 = rand() % 10 + 1;
        usleep(10);
        int data2 = rand() % 10;
        char op = opers[rand() % len];
        Task t(data1, data2, op);

        //2.生产数据
        rq->Push(t);
        cout << "Productor task done, task is : " << t.GetTask() << "who: " << name << endl;

        sleep(1);
    }
    return nullptr;
}

void *Consumer(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    string name = td->threadname;

    while(true)
    {
        //1.消费数据
        Task t;
        rq->Pop(&t);

        //2.处理数据
        t();
        cout << "Consumer get task, task is : " << t.GetTask() << "who: " << name << "result: " << t.GetResult() << endl;
    }
    return nullptr;
}

int main()
{
    srand(time(nullptr) ^ getpid());
    RingQueue<Task> *rq = new RingQueue<Task>(50);

    pthread_t c[5], p[3];

    for(int i = 0; i < 3; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Productor-" + to_string(i);

        pthread_create(p + i, nullptr, Productor, td);
    }
    for(int i = 0; i < 5; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Consumer-" + to_string(i);

        pthread_create(c + i, nullptr, Consumer, td);
    }

    for(int i = 0; i < 3; i++)
    {
        pthread_join(p[i], nullptr);
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(c[i], nullptr);
    }

    return 0;
}

十一、线程池

池化技术:以空间换时间

ThreadPool.hpp:

复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <pthread.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 5;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for(const auto &ti : threads_)
        {
            if(ti.tid == tid)
            {
                return ti.name;
            }
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while(true)
        {
            tp->Lock();

            while(tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
            std::cout << name << "run, " << "result: " << t.GetResult() << std::endl;
        }
    }
    void Start()
    {
        int num = threads_.size();
        for(int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if(nullptr == tp_)
        {
            pthread_mutex_lock(&lock_);
            if(nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }
        return tp_;
    }
private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

Main.cpp:

复制代码
#include <iostream>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "Task.hpp"
#include "ThreadPool.hpp"

int main()
{
    //获取单例对象时,也是多线程获取的
    std::cout << "process run..." << std::endl;
    sleep(3);
    ThreadPool<Task>::GetInstance()->Start();
    srand(time(nullptr) ^ getpid());
    
    while(true)
    {
        //1.构建任务
        int x = rand() % 10 + 1;
        usleep(10);
        int y = rand() % 5;
        char op = opers[rand() % opers.size()];

        Task t(x, y, op);
        ThreadPool<Task>::GetInstance()->Push(t);
        //2.交给线程池处理
        std::cout << "main thread make task: " << t.GetTask() << std::endl;

        sleep(1);
    }
    return 0;
}

十二、自旋锁

1、321原则(方便记忆)

3:三种关系,写写(互斥竞争),写读(同步互斥),读读(共享)。

2:两种角色,读者,写者,由线程承担。

1:一个交易场所,数据交换的地点。

2、读者优先的伪代码,理解rwlock实现原理

相关推荐
RisunJan2 小时前
Linux命令-man(查看Linux中的指令帮助)
linux·运维·服务器
REDcker2 小时前
CentOS 与主流 Linux 发行版:版本与时间表(年表)
linux·运维·centos
bai_lan_ya2 小时前
使用linux的io文件操作综合实验_处理表格
linux·服务器·算法
wd5205212 小时前
常用环境部署(二十九)——Centos升级OpenSSH 10.2p1
linux·运维·centos·ssh
顶点多余2 小时前
Ext文件系统详解
linux·运维·服务器
圥忈&&丅佽&&扗虖2 小时前
linux 安装 Ollama
linux·服务器
cyber_两只龙宝2 小时前
【Keepalived】抢占模式、延迟抢占模式与非抢占模式详解
linux·运维·服务器·keepalived
REDcker3 小时前
CentOS 与主流 Linux 发行版历史与版本综述
linux·centos·numpy
逻辑峰3 小时前
ReadStat在Linux的安装和使用
linux·运维·服务器