Linux下C/C++开发之多线程开发

线程概述

概念

在这里插入图片描述

进程与线程的区别

  1. 进程间难以共享,线程可共享
  2. 创建进程代价高,线程代价低
    在这里插入图片描述

线程和进程的虚拟地址空间

在这里插入图片描述

线程之间共享与非共享资源

在这里插入图片描述

NPTL 有关介绍

在这里插入图片描述

线程操作函数

创建线程—— pthread_create函数

  • 一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
    称之为子线程。
  • 程序中默认只有一个进程,fork()函数调用,2个进程
  • 程序中默认只有一个线程,pthread_create()函数调用,2个线程。
 #include <pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
    void *(*start_routine) (void *), void *arg);
  • 功能:创建一个子线程
  • 参数:
    thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
    attr : 设置线程的属性,一般使用默认值,NULL
    start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
    arg : 给第三个参数使用,传参
  • 返回值:
    成功:0
    失败:返回错误号。这个错误号和之前errno不太一样。
    获取错误号的信息: 在 #include <string.h> 下的
    char * strerror(int errnum);

示例代码

  • 注意传入参数的数据类型 void*
  • 休眠1s 是为了 防止程序过快结束,子线程还为创建调用成功,或者 sleep(1);//可以改为 pthread_exit() 函数 终止主进程
  • 注意gcc编译链接过程中的语句 后缀要加个 -pthread或者-lpthread ,(静态库、动态库中的概念)
    在这里插入图片描述
#include <pthread.h>
#include<stdio.h>
#include <unistd.h>
#include <string.h>
void * myCallback(void * arg) {
    printf("I am Child thread..\n");
    printf("recv:%d\n",*(int *)arg);//注意数据类型
    return arg;
}
int main() {
    pthread_t  pthreadID;
    int num =10;//用于测试pthread_create 最后一个形参,传递参数
    //创建子线程
    int ret = pthread_create(&pthreadID,NULL,myCallback,&num);
    if(ret == -1) {
        int _errno;
        char * errstr = strerror(_errno);
        printf("erroinfo:%s",errstr);
    }
    //主线程打印 1~10
    for(int i = 0 ;i<10;i++) {
        printf("%d\n",i);
    }
    //休眠1s 用于 子线程创建,然后调用回调函数
    sleep(1);//可以改为 pthread_exit() 函数 终止主进程
    return 0;
}

终止线程—— pthread_exit() 函数

#include <pthread.h>
void pthread_exit(void *retval);
  • 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
  • 参数:
    retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。

获取线程ID—— pthread_self() 函数

pthread_t pthread_self(void);
  • 功能:获取当前的线程的线程ID

比较线程ID(多用于跨平台)—— pthread_equal() 函数

  • int pthread_equal(pthread_t t1, pthread_t t2);
    功能:比较两个线程ID是否相等
  • 不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的
    是使用结构体去实现的。

连接已终止线程 pthread_join()函数

 #include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);
  • 功能:和一个已经终止的线程进行连接
    回收子线程的资源
    这个函数是阻塞函数,调用一次只能回收一个子线程
    一般在主线程中使用
  • 参数:
    thread:需要回收的子线程的ID
    retval: (注意是void**)接收子线程退出时的返回值(就是pthread_exit(void *retval) 中的retval)
  • 返回值:
    0 : 成功
    非0 : 失败,返回的错误号
  • 为什么是 void *? 这样可以修改 int ,详见:探讨pthread_join 第二参数是void**.

分离线程 pthread_detach()

#include <pthread.h>
    int pthread_detach(pthread_t thread);
  • 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
    1.不能多次分离,会产生不可预料的行为。
    2.不能去连接一个已经分离的线程,会报错
  • 参数:需要分离的线程的ID
  • 返回值:
    成功:0
    失败:返回错误号

取消线程 pthread_cancle()

#include <pthread.h>
    int pthread_cancel(pthread_t thread);
  • 功能:取消线程(让指定ID的线程终止)
    取消某个线程可以终止这个线程,但不是立马终止,而是当子进程执行到一个取消点时线程才会终止

  • 取消点:系统规定好的一些调用,可以理解为从用户区到内核区的切换点称为取消点。

线程属性 相关函数

  • 初始化线程属性变量
    int pthread_attr_init(pthread_attr_t *attr);

  • 释放线程属性的资源
    int pthread_attr_destroy(pthread_attr_t *attr);

  • 获取线程分离的状态属性
    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

  • 设置线程分离的状态属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

线程同步

  • 线程主要优势:可共同使用全局变量,但代价是必须确保线程不会同时操作(读写)同一变量。
  • 临界区:指访问某一共享资源的 代码段,代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应终断该片段执行。
  • 线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,进入等待状态,知道线程对内存操作结束。

互斥锁

互斥量的类型 pthread_mutex_t

  • 初始化互斥量
int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
                        const pthread_mutexattr_t *restrict attr);
  • 参数 :
    • mutex : 需要初始化的互斥量变量
    • attr : 互斥量相关的属性,NULL
    • restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
      pthread_mutex_t *restrict mutex = xxx;
      pthread_mutex_t * mutex1 = mutex;
  • 释放互斥量的资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 尝试加锁,如果加锁失败,不会阻塞,会直接返回。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

死锁

  • 死锁就是 A等着B 解锁 B等着A解锁 互相等待的状态
  • 发生死锁的三种场景
  1. 忘记释放锁
  2. 重复加锁
  3. 多线程多锁,抢占锁资源

读写锁

  • 特点
  1. 如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作
  2. 如果有其他线程写数据,则其他线程都不允许读、写操作
  3. 写是独占的,写的优先级高

相关函数

注意方框的不同。
在这里插入图片描述

条件变量

条件变量的类型 pthread_cond_t

  • 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);
  • 销毁
    int pthread_cond_destroy(pthread_cond_t *cond);
  • 等待,调用了该函数,线程会阻塞。
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
  • 唤醒一个或者多个等待的线程
    int pthread_cond_signal(pthread_cond_t *cond);
  • 唤醒所有的等待的线程
    int pthread_cond_broadcast(pthread_cond_t *cond);

信号量

  • 信号量的类型 sem_t

  • int sem_init(sem_t *sem, int pshared, unsigned int value);

    • 初始化信号量

    • 参数:

    • sem : 信号量变量的地址

    • pshared : 0 用在线程间 ,非0 用在进程间

    • value : 信号量中的值

      int sem_destroy(sem_t *sem);
      - 释放资源

      int sem_wait(sem_t *sem);
      - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

  • int sem_trywait(sem_t *sem);

  • int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  • int sem_post(sem_t *sem);

    • 对信号量解锁,调用一次对信号量的值+1
  • int sem_getvalue(sem_t *sem, int *sval);

  • 伪代码 (模拟生产者和消费者模型)

sem_t psem;
sem_t csem;
//初始化
init(psem, 0, 8);
init(csem, 0, 0);

producer() {
    sem_wait(&psem);//为零阻塞  不为零则如同减去1
    sem_post(&csem);//加一
}

customer() {
    sem_wait(&csem);//为零阻塞 不为零则如同减去1
    sem_post(&psem);//加一
}
  • 0
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论

打赏作者

yyjshang

分享有益很是庆幸,感谢支持!

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值