Linux | Linux中的线程、互斥量、信号量的基本使用

前言

一、概念

类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。

1.1、什么是线程

线程(LWP)是操作系统能够进行运算调度的最小单位,是轻量级的进程,其本质仍是进程

线程是轻量级的进程,则线程与它的区别有哪些?

  • 与进程相比,线程没有独立的地址空间(共享);
  • 在Linux下,线程是最小的执行单位;进程是最小分配资源的单元(一个线程的进程);

1.2、实现原理

进程与线程关系密切:

  • 线程也有PCB,创建线程使用的底层函数和进程一样,都是clone;
  • 每个线程都有自己不同的PCB,但PCB中下指向内存资源的三级页表是相同的;
  • 进程可以蜕变成线程;
  • 线程可看做寄存器和栈的集合;

1.3、线程共享资源

  • 文件描述符表;
  • 每种信号的处理方式;
  • 当前工作目录;
  • 用户ID和组ID;
  • 内存地址空间(.text/.data/.bss/heap/共享库);

1.4、线程非共享资源

  • 线程ID;
  • 处理器线程和栈指针(内核栈);
  • 独立的栈空间(用户空间栈);
  • errno变量;
  • 信号屏蔽字;

1.5、线程优缺点

优点

  • 提高程序并发性;
  • 开销小;
  • 数据通信、共享数据方便;

缺点

  • 库函数,不稳定;
  • 调试困难;
  • 对信号支持不友好;

1.6、并行和并发

并发

在单核上,多个线程占用不同的CPU时间片,物理上还是串行执行的,但是由于每个线程占用的CPU时间片非常短(比如10ms),看
起来就像是多个线程都在共同执行一样,这样的场景称作并发(concurrent);

在这里插入图片描述

并行

在多核或者多CPU上,多个线程是在真正的同时执行,这样的场景称作并行(parallel);

在这里插入图片描述
【q】task1一直保持在一个核上调度有好处

  • 由于CPU在执行任务的时候会将任务的数据指令缓存,即不需要访问磁盘,直接访问CPU,若在不同核上切换会增加开销,由于cache会失效;

二、多线程的优势以及类型

【q】多线程就一定好嘛:不一定,需要看具体的应用场景,如下;

2.1 IO密集型

  • 该类型在程序指令上做IO操作(设备、网络、文件),会存在IO阻塞(该阶段可使用多线程将时间片让出);
  • 无论是CPU是单、多核,多CPU都较适用多线程程序;

2.2 CPU密集型

  • 一般程序在指令都是做计算用的;
    单核
  • 在单核情况下,由于只有一个核在进行运算,即使多个线程也是使用同一个,不能同时进行运算,故在一个线程运算好需要切换到另外一个线程继续运算,而切换的时候存在额外的开销,故低于单线程;

多核

  • 多线程可以并行执行,对CPU利用率好;
  • 该类型更适合设计为多线

三、线程数量的确定

3.1 线程的消耗

【p】为了完成任务,创建很多的线程可以吗?线程真的是越多越好?

  • 线程的创建和销毁都是非常"重"的操作
    • 涉及到用户空间到内核空间的切换;
  • 线程栈本身占用大量内存
    • 3G用户空间大概可以创建380个线程(一个线程栈8M);
  • 线程的上下文切换要占用大量时间
  • ·大量线程同时唤醒会使系统经常出现锯齿状负载或者瞬间负载量很大导致宕机

3.2 线程池的优势

  • 优势是在服务进程启动时,就事先创建好线程,当业务流量到来时需要分配线程,直接从线程池中获取一个空闲线程执行task任务即可,task执行完成后,也不用释放线程,而是把线程归还到线程池中继续给后续的task提供服务,以此来提高实时性;

【1】fixed模式线程池

  • 线程池里面的线程个数是固定不变的,一般是ThreadPool创建时根据当前机器的CPU核心数量进行指定;

【2】cached模式线程池

  • 线程池里面的线程个数是可动态增长的,根据任务的数量动态的增加线程的数量,但是会设置一个线程数
    量的阈值,任务处理完成,如果动态增长的线程空闲了60s还没有处理其它任务,那么关闭线程,保持池中最初数量的线程即可。

四、线程常用方法(self、create、exit、join、detach、cancel)

pthread_self

/**
 * pthread_t pthread_self(void)           
 * -------
 * func:获取线程ID(不同进程的线程ID可相同);
 * return:返回调用的线程ID;
 * */

pthread_create

/**
 * int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);      
 * -------
 * func:创建一个新进程;
 * param thread:传出参数,保存创建的线程
 * return:成功返回0;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: threadDemo.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Tue 28 Dec 2021 08:03:53 PM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *test_func(void *arg){

    cout << (int *)arg <<  "th I am 【" << pthread_self()  << "】thread" << endl;
    
    return nullptr;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    int ret, i;

    for(i=0; i < 5; ++i){
        if((ret = pthread_create(&tid, NULL, test_func, (void *)i)) != 0){
            cout << "pthread error" << endl;
            exit(1);
        }
    }
    pthread_exit(NULL);

}

在这里插入图片描述


pthread_exit

/**
 * void pthread_exit(void *retval);
 * -------
 * func:将单个线程退出;
 * param thread:传出参数,接收线程退出状态,通常传入NULL;
 * */

pthread_join

/**
 * int pthread_join(pthread_t thread, void **retval);
 * -------
 * func:阻塞等待线程退出,获取线程退出状态;
 * param thread:传入线程ID;
 * param retval:传出参数,记录退出状态;
 * return:成功返回0,失败返回错误信号;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: threadRecycle.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Tue 28 Dec 2021 10:59:16 PM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

typedef struct {
    std::string name;
    int age;
}exit_t;

void *func(void *){
    exit_t *m_t = new exit_t;
    m_t->name = "xiaomi";
    m_t->age = 15;
    
    cout << "I am thread..." << endl;

    pthread_exit((void *)m_t);
}


int main(int argc, char* argv[])
{
    pthread_t tid;
    exit_t *m_t;
    int ret;

    cout << "I am main..." << endl;
    if((ret = pthread_create(&tid, NULL, func, NULL)) != 0){
        cout << "create error" << endl;
        exit(1);
    }

    pthread_join(tid, (void **)&m_t);
    cout << "name: 【" << m_t->name
        << "】" << " age:【"
        << m_t->age << "】" << endl;
    pthread_exit((void *)1);
    

    return 0;
}
 	

在这里插入图片描述


pthread_detach

/**
 * int pthread_detach(pthread_t thread);
 * -------
 * func:使线程分离,指定该状态后,线程主动与主控线程断开关系,线程结束后,自动清理;
 * param thread:传入线程ID;
 * param retval:传出参数,记录退出状态;
 * return:成功返回0,失败返回错误信号;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: detch.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 08:35:39 AM CST
----------------------------------------------------------------------*/

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

using namespace std;

void *func(void *arg){
    cout << "I am pthread" << endl;

    return (void *)1;
}

/*
 * detach pass by set attr
 * -----------------------
 * pthread_attr_t attr;
 * pthread_attr_init(&attr); // 初始化
 * pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 * pthrea_create(&tid, &attr, func, NULL);
 *
 * */

int main(int argc, char* argv[])
{
    pthread_t tid;

    pthread_create(&tid, NULL, func, NULL);

    cout << "I am main" << endl;

    int ret = pthread_detach(tid);
    cout << "detach pthread => " << ret << endl;

    return 0;
}


pthread_cancel

/**
 * int pthread_cancel(pthread_t thread);
 * -------
 * func:杀死线程;
 * param thread:传入线程ID;
 * return:成功返回0,失败返回错误信号;
 * 【注意】:杀死线程需要达到取消点
 * */

案例

/*----------------------------------------------------------------------
	> File Name: killpthread.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 08:48:57 AM CST
----------------------------------------------------------------------*/

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

using namespace std;


void *func(void *arg){
    while(1){
        cout << "---------------" << endl;
        sleep(1);
    }
    pthread_testcancel();
}

int main(int argc, char* argv[])
{
    pthread_t tid;

    pthread_create(&tid, NULL, func, NULL);

    cout << "I am main" << endl;
    sleep(3);
    pthread_cancel(tid);
    
    return 0;
}

五、线程同步

可参考【C++模块实现】| 【07】对于互斥、自旋锁、条件变量、信号量简介及封装

5.1概念

指一个线程发出某一个功能时,在没有得到结果之前,该调不返回。同时其他线程为保证数据一致性,不能调用该功能;

  • 用于多个控制流,共同操作一个共享资源的情况;

数据混乱原因

  • 资源共享;
  • 调度随机;
  • 线程间缺乏必要的同步机制;

5.2 互斥量

5.2.1 基本概念

每个线程在对资源操作前都尝试先加锁,成功上锁才能操作,操作结束后解锁,确保在于时间有关的操作上不会出现错误;

5.2.2 常用函数

pthread_mutex_init

/**
 * int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
 * -------
 * func:初始化一个互斥锁;
 * param mutex:传入线程ID;
 * attr:默认为线程共享,通常传入NULL;
 * 		静态初始化:直接通过宏进行初始化`pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER`;
 * 		动态初始化:局部变量;
 * return:成功返回0;

 * */

pthread_mutex_destroy

/**
 * int pthread_mutex_destroy(pthread_mutex_t *mutex);
 * -------
 * func:销毁一个互斥锁;
 * param mutex:传入线程ID;
 * return:成功返回0;
 * */

pthread_mutex_lock

/**
 * int pthread_mutex_lock(pthread_mutex_t *mutex);
 * -------
 * func:加锁;
 * param mutex:传入线程ID;
 * return:成功返回0;
 * */

pthread_mutex_unlock

/**
 * int pthread_mutex_unlock(pthread_mutex_t *mutex);
 * -------
 * func:解锁;
 * param mutex:传入线程ID;
 * return:成功返回0;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: lock.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 10:38:32 AM CST
-----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

pthread_mutex_t mutex;


void *func(void *arg){

    while(1){
        pthread_mutex_lock(&mutex);
        cout << "I am pthread" << endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }
    return (void *)1;
}

int main(int argc, char* argv[])
{
    pthread_t tid;

    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid, NULL, func, NULL);

    while(1){
        pthread_mutex_lock(&mutex);
        cout << "I am main" << endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }

    pthread_join(tid, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

在这里插入图片描述

5.3 读写锁

5.3.1 状态
  • 读模式下加锁状态;
  • 写模式下加锁状态;
  • 不加锁状态;
5.3.2 特性
  • 写锁优先级别高;
  • 写独占,读共享;
5.3.3 案例
/*----------------------------------------------------------------------
	> File Name: rw_lock.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 11:31:26 AM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

int count;
pthread_rwlock_t rwlock;

/** 读 */
void *r_func(void *arg){
    while(1){
        pthread_rwlock_wrlock(&rwlock);
        cout << "【read】 data = " << count << " thread_id: " << pthread_self() << endl;
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }

    return nullptr;
}

/** 写 */
void *w_func(void *arg){

    while(1){
        pthread_rwlock_wrlock(&rwlock);
        cout << "【write】  " << count++ << " thread_id: " << pthread_self() << endl;
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return nullptr;
}

int main(int argc, char* argv[])
{
    
    int i;
    pthread_t pid[8];

    pthread_rwlock_init(&rwlock, NULL);
    /* 创建3个写进程 */
    for(i=0; i<3; ++i)
        pthread_create(&pid[i], NULL, w_func, NULL);

    /* 创建5个读进程 */
    for(i=0; i<5; ++i)
        pthread_create(&pid[i+3], NULL, r_func, NULL);

    for(i=0; i<8; ++i){
        pthread_join(pid[i], NULL);
    }
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

在这里插入图片描述

5.4 信号量

========》简介参考《========

sem_init

/**
 *  int sem_init(sem_t *sem, int pshared, unsigned int value);
 * -------
 * func:初始化信号量;
 * param sem:传入创建的条件变量;
 * param pshared:为0时,表示线程间共享,否则为进程间共享;
 * param value:指定信号量的初始值【决定信号量线程的个数】;
 * return:成功返回0,失败返回-1;
 * */

sem_destroy

/**
 *  int sem_destroy(sem_t *sem);
 * -------
 * func:销毁信号量;
 * param sem:传入信号量;
 * return:成功返回0,失败返回-1;
 * */

sem_wait

/**
 * int sem_wait(sem_t *sem);
 * -------
 * func:若信号量大于0,则信号量递减,若信号量为0,则阻塞等待;
 * param sem:传入信号量;
 * return:成功返回0,失败返回-1;
 * */

sem_post

/**
 * int sem_post(sem_t *sem);
 * -------
 * func:将信号量递增,同时唤醒阻塞在信号量上的线程;
 * param sem:传入信号量;
 * return:成功返回0,失败返回-1;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: mul_pro_cons.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 08:19:26 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
using namespace std;

/**
 * 生产者消费者模式,使用cond函数进行等待、接收信号
 * */
int queue[5];
sem_t blank_n, product_n;

/** 生产者 */
void *product_func(void *arg){
    
    int i=0;
    while(1){
        sem_wait(&blank_n);
        queue[i%5] = rand() & 100;
        cout << "produce " << queue[i%5] << endl;
        sem_post(&product_n);

        i++;
        sleep(2);
    }
    return NULL;
}

/** 消费者 */
void *comsump_func(void *arg){
    
    int i=0;
    while(1){
        sem_wait(&product_n);
        cout << "consume " << queue[i%5] << endl;
        queue[i%5] = 0;
        sem_post(&blank_n);

        i++;
        sleep(2);
    }

    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t ptid, ctid;
    sem_init(&blank_n, 0, 5);
    sem_init(&product_n, 0, 0);

    pthread_create(&ptid, NULL, product_func, NULL);
    pthread_create(&ctid, NULL, comsump_func, NULL);

    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);

    sem_destroy(&blank_n);
    sem_destroy(&product_n);


    return 0;
}

在这里插入图片描述


5.5 条件变量

本身不是锁,可造成线程阻塞。通常与互斥锁配合使用,给多线程提供一个会合场所;

pthread_cond_init

/**
 * int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
 * -------
 * func:初始化一个条件变量;
 * param cond:接收创建的条件变量;
 * 		静态初始化可使用`pthread_cond_t cond = PTHREAD_COND_INITIALIZER`;
 * param attr:配置条件变量属性,通常传NULL;
 * return:成功返回0;
 * */

pthread_cond_destroy

/**
 *  int pthread_cond_destroy(pthread_cond_t *cond);
 * -------
 * func:销毁一个条件变量;
 * param cond:传入创建的条件变量;
 * return:成功返回0;
 * */

pthread_cond_wait

/**
 *  int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);
 * -------
 * func:阻塞等待一个条件变量,释放已掌握的互斥锁,当被唤醒,返回时,接触阻塞并重新申请获取互斥锁;
 * param cond:传入创建的条件变量;
 * param mutex:互斥锁;
 * return:成功返回0;
 * */

pthread_cond_signal

/**
 *  int pthread_cond_signal(pthread_cond_t *cond);
 * -------
 * func:接触阻塞的条件变量;
 * param cond:传入创建的条件变量;
 * return:成功返回0;
 * */

pthread_cond_broadcast

/**
 *  int pthread_cond_broadcast(pthread_cond_t *cond);
 * -------
 * func:解除当前在指定条件变量cond上阻塞的所有线程;
 * param cond:传入创建的条件变量;
 * return:成功返回0;
 * */

案例

/*----------------------------------------------------------------------
	> File Name: pro_cons.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Wed 29 Dec 2021 06:15:52 PM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <vector>
using namespace std;

/**
 * 生产者消费者模式,使用cond函数进行等待、接收信号
 * */
vector<int> vc;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;; 

/** 生产者 */
void *product_func(void *arg){
    
    while(1){
        pthread_mutex_lock(&mutex);
        vc.push_back(rand() % 100);
        cout << "product 【" << vc[0] << "】" << endl;
        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&has_product);

        sleep(2);
    }
    return NULL;
}

/** 消费者 */
void *comsump_func(void *arg){
    
    while(1){
        pthread_mutex_lock(&mutex);
        while(vc.empty()){
            pthread_cond_wait(&has_product, &mutex);
        }
        cout << "comsump 【" << vc[0] << "】" << endl;
        vc.pop_back();
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }

    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t ptid, ctid;

    pthread_create(&ptid, NULL, product_func, NULL);
    pthread_create(&ctid, NULL, comsump_func, NULL);

    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);
    return 0;
}

在这里插入图片描述

5.6 可重入和不可重入

  • 不可重入:首先看该段代码是否能在多线程下执行就是是否存在竞态条件(即不可重入,随着线程的调度顺序不同,会得到不同的运行结果),需要在临界区保证他的原子操作;
  • 可重入:在多线程下不存在竞态条件;
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值