首页 > 其他分享 >30_多线程

30_多线程

时间:2024-04-08 12:11:23浏览次数:31  
标签:attr int cond 30 线程 pthread mutex 多线程

多线程

多线程简介

​ 线程,是一种允许一个正在运行的程序同时执行不止一个任务的机制。不同线程看起来是并行运行的;Linux操作系统对线程进行异步调度,不断中断它们的执行以给其它线程执行的机会。
​ 线程与进程的区别:

  • 线程是进程中的一个独立并发执行的路径,进程退出时,线程也会退出;
  • 系统按照进程来分配资源,线程共享所在进程的运行地址空间对应的资源,但是每个线程有自己的栈空间;
  • 进程切换的开销要远大于线程;

线程函数说明

pthread_create

/**
 * @brief 创建一个新的线程,新的线程从start_routine开始执行,创建时通过arg给线程传递参数 
 * @param[out] thread 成功后,函数返回线程的ID保存在此变量中,用于区分和后续的线程操作
 * @param[in] attr 线程属性,可以在创建线程时设置线程的属性,比如是否可被join、线程运行优先级的设置
 * 在实际开发中用的比较多
 * @param[in] start_routine 线程创建成功后,从这个函数开始运行,由用户自己实现线程的函数 
 * @param[in] arg
 * @return 成功返回0,调用失败返回错误码
 * @note 编译链接时需指定-lpthread库
**/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *
(*start_routine) (void *), void *arg);

pthread_cancel

/**
 * @brief 终止一个正在运行的线程执行
 *
 * @param[in] thread 表示想要终止的线程ID
 *
 * @return 成功返回0,错误返回错误码
 * @note 编译链接时需指定-lpthread库
**/
int pthread_cancel(pthread_t thread);

pthread_join

/**
* @brief 函数会一直阻塞调用它的线程,直至目标线程执行结束(接收到目标线程的返回值),阻塞状态才会解除
*
* @param[in] thread 表示要等待的线程的ID
* @param[out] retval 表示线程结束返回的值,注意线程返回的是一个void*的指针,因此需要使用二级指针
* 接收返回值
* @return 成功返回0,错误返回错误码
* @note 编译链接时需指定-lpthread库
* */
int pthread_join(pthread_t thread, void **retval);

示例代码

pthread.c

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

void *pthread_start_routine(void *args)
{
    int *nCount = args;
    while(1)
    {
        sleep(1);
        (*nCount)++;
        if(*nCount > 2)
        {
            break;
        }
        printf("pthread count:%d\n", *nCount);
        printf("pthread_start_routine\n");
    }
    return "I'm died";
}

int main(int argc, const char* argv[])
{
    pthread_t tid;
    int nCount = 0;
    int nRet;
    int *pRetVal = NULL;

    nRet = pthread_create(&tid, NULL, pthread_start_routine, (void *)&nCount); //创建线程
    if(nRet != 0)
    {
        perror("pthread_create is error\n");
        return -1;
    }
    pthread_join(tid, &pRetVal); //阻塞等待线程释放
    printf("ret:%s\n", pRetVal);
    return 0;
}

运行结果

image-20240407191329152

pthread_attr_init

/**
* @brief 初始化一个线程的属性对象
*
* @param attr 表示要设置线程属性的结构体
* 
* 接收返回值
* @return 调用成功返回0,否则返回错误码
* 
* */
int pthread_attr_init(pthread_attr_t *attr);

pthread_attr_destroy

/**
* @brief 释放线程的属性对象
*
* @param attr 表示要设置线程属性的结构体
* 
* 接收返回值
* @return 成功返回0,错误返回错误码
* 
* */
int pthread_attr_destroy(pthread_attr_t *attr);

pthread_attr_setdetachstate

/**
* @brief 设置线程可设置为是否可被joinable或者detached状态
*
* @param attr 表示线程属性结构体指针
* @param detachstate 设置detached属性 使用宏PTHREAD_CREATE_JOINABLE(等待别人释放自己)、PTHREAD_CREATE_DETACHED(不用等待别人释放自己,自己结束时清理自己的资源)
* 接收返回值
* @return 成功返回0,错误返回非0值。
* 
* */
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

示例代码

pthread.c

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

void *pthread_start_routine(void *args)
{
    int *nCount = args;
    while (1)
    {
        sleep(1);
        (*nCount)++;
        if (*nCount > 2)
        {
            break;
        }
        printf("pthread count:%d\n", *nCount);
        printf("pthread_start_routine\n");
    }
    return "I'm died";
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int nCount = 0;
    int nRet;
    int *pRetVal = NULL;
    pthread_attr_t *attr;

    pthread_attr_init(&attr); //初始化线程的属性对象
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程可设置为是否可被joinable或者detached状态

    nRet = pthread_create(&tid, &attr, pthread_start_routine, (void *)&nCount); // 创建线程
    if (nRet != 0)
    {
        perror("pthread_create is error\n");
        return -1;
    }
    pthread_attr_destroy(&attr); //释放线程的属性对象
    while (1)
    {
        sleep(1);

        printf("main\n");
    }
    return 0;
}

运行结果

image-20240407205902448

线程优先级系列说明

Linux内核的三种调度策略:
1.SCHED_OTHER 分时调度策略
2.SCHED_FIFO 实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃
3.SCHED_RR实 时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平
Linux线程优先级设置:
首先,可以通过以下两个函数来获得线程可以设置的最高和最低优先级,函数中的策略即上述三种策略的宏定义:

int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

注意:SCHED_OTHER 是不支持优先级使用的,而 SCHED_FIFO 和 SCHED_RR 支持优先级的使用,他们分别为1和99,数值越大优先级越高。
设置和获取优先级通过以下两个函数:

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

param.sched_priority = 51; //设置优先级

​ 系统创建线程时,默认的线程是 SCHED_OTHER。所以如果我们要改变线程的调度策略的话,可以通过下面的这个函数实现。

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

​ 上面的param使用了下面的这个数据结构:

struct sched_param
{
	int __sched_priority; // 所要设定的线程优先级
};

线程的竞争与互斥mutex

线程锁mutex简介

多个线程的同时执行,导致了一个问题:同一个资源可能会被多个线程同时访问,这个叫资源的竞争。
为了解决多线程导致的资源竞争问题,就要保证竞争的资源在被访问时是互斥的,不能同时访问。
线程的mutex可以看作是一把锁,当执行到竞争的资源时,执行以下步骤,完成资源的互斥访问,解决竞争的问题。

  • 执行到竞争资源时,先获取锁。未上锁则执行竞争资源,如果被别人获取到,则睡眠等待;

  • 执行完竞争资源的代码后,释放锁,并唤醒因这把锁睡眠的线程;

通过以上两点,可以保证竞争资源的互斥访问,通过竞争同一锁的方式来得到执行竞争资源的机会。

线程锁相关函数说明

pthread_mutex_init

/**
* @brief 初始化一个互斥锁,以mutexattr的方式创建
*
* @param[in] mutex 互斥量指针
* @param[in] mutexattr 互斥量属性指针,默认传递NULL,除非有以下使用需求,一般地可以不关心
* 通过pthread_mutexattrattr_setpshared函数设置PTHREAD_PROCESS_SHARED使得进程间共享锁
* 通过pthread_mutexattr_gettype/pthread_mutexattr_settype获取和设置锁的类型,有以下几种
* 	PTHREAD_MUTEX_NORMAL/PTHREAD_MUTEX_ERRORCHECK/PTHREAD_MUTEX_RECURSIVE,
* 	默认使用PTHREAD_MUTEX_DEFAULT,不检测死锁,当重复锁定一个互斥量时会导致死锁,也不能释放别人锁定的锁
* 	PTHREAD_MUTEX_ERRORCHECK是递归锁,可以重复上锁,但是必须当前线程执行相同的锁定和解锁,别的线程才能得到这把锁
* 	PTHREAD_MUTEX_ERRORCHECK会自动检测死锁,如果一个线程试图对一个互斥锁重复锁定,将会返回一个错误代码
* 也可以通过以下的方式快速设置一把锁的属性:
* 	pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
* 	pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
* 	pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
*
* @return 成功返回0,错误返回错误码
* */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

pthread_mutex_lock

/
* @brief 获取互斥锁
*
* @param[in] mutex 互斥量指针
* @return 成功返回0,错误返回错误码
* */
int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_trylock

/**
* @brief 尝试获取互斥锁,获取不到不会死锁,只会返回错误
*
* @param[in] mutex 互斥量指针
* @return 成功返回0,错误返回错误码
* */
int pthread_mutex_trylock(pthread_mutex_t *mutex);

pthread_mutex_unlock

/**
* @brief 互斥量解锁,并唤醒等待这把锁而睡眠的线程
*
* @param[in] mutex 互斥量指针
* @return 成功返回0,错误返回错误码
* */
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_destroy

/**
* @brief 释放互斥量
*
* @param[in] mutex 互斥量指针
* @return 成功返回0,错误返回错误码
* */
int pthread_mutex_destroy(pthread_mutex_t *mutex);

线程锁示例代码说明

​ 以下代码创建了一个线程,主线程和子线程分别打印大小写的hello和world。但是hello和world中会有睡眠等待,在不用互斥锁的情况下可能的打印为:"hello, HELLO,WORLD world"这种交叉的方式。
​ 我们将通过互斥锁的方式,保证两个线程都能够正常交替的打印完整的信息,预期的打印结果如下:

hello,world
HELLO,WORLD

线程锁示例代码

pthread_mutex.c

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

pthread_mutex_t mutex;

void *start_pthread(void *args)
{
    while(1)
    {
        sleep(1);
        pthread_mutex_lock(&mutex); //获取互斥锁
        printf("hello, ");
        fflush(stdout);
        sleep(1);
        printf("world\n");
        fflush(stdout);
        pthread_mutex_unlock(&mutex); //互斥量解锁
    }
}

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

    ret = pthread_create(&tid, NULL, start_pthread, NULL); //创建线程
    if(ret != 0)
    {
        printf("pthread_create is error\n");
        return -1;
    }
    ret = pthread_mutex_init(&mutex, NULL); //初始化一个互斥锁
    if(ret != 0)
    {
        printf("pthread_mutex_init is error\n");
        return -1;
    }
    while(1)
    {
        sleep(1);
        pthread_mutex_lock(&mutex); //获取互斥锁
        printf("HELLO, ");
        fflush(stdout);
        sleep(1);
        printf("WORLD\n");
        fflush(stdout);
        pthread_mutex_unlock(&mutex); //互斥量解锁
    }
    pthread_join(tid, NULL); //阻塞等待进程结束
    pthread_mutex_destroy(&mutex); //释放互斥量
    return 0;
}

运行结果

image-20240407225308795

image-20240407225341934

线程的条件变量

线程条件变量简介

​ 线程执行在有些时候会需要一个条件才能继续执行代码,如果条件不成立,线程最好能够睡眠,以减轻对CPU造成的负担。
​ 线程条件变量既是这样一种机制:

  • 线程A在条件不满足时,通过线程条件变量睡眠等待条件成立,直到等到其他线程改变满足的条件并唤醒;
  • 线程B改变了线程A满足执行的条件,并唤醒A线程;

线程条件变量相关函数说明

pthread_cond_init

/**
* @brief 初始化线程条件变量
*
* @param[in] cond 线程条件变量指针
* @param[in] cond_attr 线程条件变量属性指针,默认传递NULL,可以通过以下设置为线程/进程间共享
* 通过pthread_condattr_getpshared/pthread_condattr_setpshared设置为线程或进程有效范围
* 也可以通过以下的方式快速设置一把锁的属性:
* 	pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
* 	pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
* 	pthread_mutex_t errchkmutex =PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
*
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

pthread_cond_signal

/**
* @brief 通知唤醒一个在cond上睡眠的线程
*
* @param[in] cond 线程条件变量
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast

/**
* @brief 通知唤醒所有在cond上睡眠等待的线程
*
* @param[in] cond 线程条件变量
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_wait

/**
* @brief 解锁mutex,并在cond条件上睡眠等待
*
* @param[in] cond 条件变量指针
* @param[in] mutex 互斥量指针
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_timedwait

/**
* @brief 解锁mutex,并在cond条件上睡眠等待abstime长的时间
*
 *@param[in] cond 条件变量指针
* @param[in] mutex 互斥量指针
* @param[in] abstime 睡眠等待的时间
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

pthread_cond_destroy

/**
* @brief 释放条件变量
*
* @param[in] cond 线程条件变量
* @return 成功返回0,错误返回错误码
* */
int pthread_cond_destroy(pthread_cond_t *cond);

线程条件变量代码实现

​ 以下代码实现了g_nFlag不大于0的时候,子线程阻塞等待。
​ 直到另一个线程打破这个条件,并通过pthread_cond_signal/pthread_cond_broadcast唤醒由pthread_cond_wait睡眠的线程,继续执行代码。
​ 这里g_nFlag被看作是一个任务,子线程没有任务的时候(g_nFlag<=0),子线程睡眠,当有任务(g_nFlag>0)时,子线程执行代码。虽然代码简单,但是它是一种事件驱动方式的编程实践,是一种程序设计的方式,开发者应该掌握和熟练使用它,以使得代码能够更高效的运行任务。

pthread_cond.c

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

int g_nFlag = 0;
pthread_mutex_t mutex; // 线程互斥锁
pthread_cond_t cond;   // 线程条件变量

void *start_pthread(void *args)
{
    while (1)
    {
        pthread_mutex_lock(&mutex); // 获取互斥锁
        while (g_nFlag <= 0)
        {
            pthread_cond_wait(&cond, &mutex); // 解锁mutex,并在cond条件上睡眠等待
        }
        pthread_mutex_unlock(&mutex); // 互斥量解锁
        g_nFlag--;
        printf("after pthread_cond_wait\n");
    }
    return NULL;
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int ret;
    ret = pthread_create(&tid, NULL, start_pthread, NULL); // 创建线程
    if (ret != 0)
    {
        printf("pthread_create is error!\n");
        return -1;
    }
    ret = pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    if (ret != 0)
    {
        printf("pthread_mutex_init is error!\n");
        return -1;
    }
    ret = pthread_cond_init(&cond, NULL); //初始化线程条件变量
    if (ret != 0)
    {
        printf("pthread_cond_init is error!\n");
        return -1;
    }
    while (1)
    {
        printf("input value:");
        scanf("%d", &g_nFlag);
        pthread_mutex_lock(&mutex); // 获取互斥锁
        pthread_cond_signal(&cond); // 通知唤醒一个在cond上睡眠的线程
        pthread_mutex_unlock(&mutex); // 互斥量解锁
    }
    pthread_join(tid, NULL);       // 阻塞等待线程释放
    pthread_cond_destroy(&cond);   // 释放条件变量
    pthread_mutex_destroy(&mutex); // 释放互斥量
    return 0;
}

运行结果

image-20240407235214712

实现万金油任务分发器

万金油任务分发器简介

​ 有一类线程是事件驱动型的,当收到消息后,就开始执行代码,没有收到消息时,就睡眠等待。我们把线程执行的消息看作是一个任务,即线程任务。
​ 线程要执行任务,需要另一个线程提供任务消息,因此需要定义一个消息的队列来存放这一类任务消息,以下代码提供了一个线程安全的消息队列,并且使用线程的互斥锁和条件变量来完成任务的收发、执行的过程。
​ 为什么叫万金油任务分发器?这是因为从设计角度说,我们总是希望一个线程专门做一类任务,而不是把一个线程搞的很复杂和臃肿,并且这类任务由其他线程分发过来,我们只管有消息执行,无消息睡眠即可。以下代码抽象了基于线程安全的任务分发的机制,调用者只用关心处理自己的消息即可。

标签:attr,int,cond,30,线程,pthread,mutex,多线程
From: https://www.cnblogs.com/mzx233/p/18120851

相关文章

  • [INS-30131] 执行安装程序验证所需的初始设置失败
    一、基础环境  操作系统:MicrosoftWindowsServer2012R2Standard(64位)  数据库版本:OracleDatabase11.2.0.4.0(64位)二、问题详情    在安装过程中,报错:[INS-30131]执行安装程序验证所需的初始设置失败。点击【详细信息】,弹出以下内容:附加信息:所有节点上的框架设置检......
  • 蓝桥杯—DS1302
    目录1.管脚2.时序&官方提供的读写函数3.如何使用读写函数4.如何在数码管中显示在DS1302中读取出的数据?1.管脚2.时序&官方提供的读写函数/* # DS1302代码片段说明 1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。 2. 参赛选手可以自行编写相关代码或......
  • 30 天精通 RxJS (09):Observable Operator - skip, takeLast, last, concat, startWith, merge
    运营商skip我们昨天介绍了take可以取前几个送出的元素,今天介绍可以略过前几个送出元素的operator:skip,范例如下:varsource=Rx.Observable.interval(1000)varexample=source.skip(3)example.subscribe({ next:(value)=>{ console.log(value) }, error:(err)......
  • Java多线程
    Process进程系统资源分配的单位Thread线程CPU调度和执行的单位1.继承Thread类packagedemo01;//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程//线程开启不一定立即执行,由cpu调度publicclassTestThread1extendsThread{@Overridep......
  • 洛谷B3835 [GESP202303 一级] 每月天数
    这道题是让我们输出给定的月份有多少天#include<bits/stdc++.h>usingnamespacestd;intmain(){ intyear,month;cin>>year>>month;if(month==1||month==3||month==5||month==7||month==8||month==10||month==12){cout<<31;......
  • 洛谷B3840 [GESP202306 二级] 找素数
    这道题让我们找A 和 B 之间(包括 A 和 B)有多少个素数。#include<bits/stdc++.h>usingnamespacestd;boolisprime(intn){if(n==0||n==1)returnfalse;for(inti=2;i*i<=n;i++){if(n%i==0)returnfalse;}returntrue;}intmain(){......
  • 【51单片机入门记录】RTC(实时时钟)-DS1302应用
    目录一、DS1302相关写函数(1)Write_Ds1302(2)Write_Ds1302_Byte二、DS130相关数据操作流程及相关代码(1)DS1302初始化数据操作流程及相关代码(shijian[i]/10<<4)|(shijian[i]%10)的作用:将十进制转换为BCD码。代码呈现(2)DS1302获取数据操作流程及相关代码代码呈现三、应用举例-......
  • 多线程
    参考转载自:.NET多线程-Broder-博客园(cnblogs.com)一、进程和线程定义进程:一个程序在服务器上运行时,占用的计算机资源合集,就是进程。线程:是程序能够独立运行的最小单位。线程具有进程所具有的特征,所以线程又叫轻型进程。二、多线程线程分为原生线程和托管线程,原生线程生......
  • ACwing830 单调栈
    这道题是P8600[蓝桥杯2013省B]连号区间数的前置知识#include<iostream>#include<stdio.h>#include<algorithm>#include<string>#include<cmath>#defineR(x)x=read()#defineFor(i,j,n)for(inti=j;i<=n;++i)usingnamespa......
  • 多线程环境中使用MyBatis时避免出现没有token的问题
    //很重要,在多线程没有token下面三行可以解决mybatis方法报错//1.通过当前的WebUtil.getRequest()获取Servlet请求的属性ServletRequestAttributesservletRequestAttributes=newServletRequestAttributes(WebUtil.getRequest());//2.将获取到的Servlet请求属性设置......