16.Linux实战开发4.0

16.Linux实战开发4.0

1、线程

1、概念:线程由进程演化而来,是CPU调度和分配任务的最小单位,一个进程可以有多个线程,和其他进程一块共享一个进程的资源空间。

进程:进程是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。

2、两者关系:线程是依赖进程而存在的,进程结束,线程会直接结束。

两者区别:

  1. 资源占用:进程是程序的执行实例,每个进程都有独立的内存空间和系统资源(如文件、网络连接等),因此多个进程之间相互独立。而线程是在进程内部执行的,多个线程共享同一进程的资源,包括内存和文件句柄等。
  2. 并发性:由于多个线程共享进程的资源,在多核系统上可以并行执行,从而提高效率。而进程之间需要通过进程间通信(IPC)来进行数据交换,相对较为复杂。
  3. 调度和切换:线程是调度和切换的基本单位,由操作系统负责线程的调度和切换,切换代价相对较小。而进程的调度和切换代价较大,因为需要保存和恢复更多的上下文信息。
  4. 创建和销毁:创建和销毁线程的开销较小,通常只涉及栈空间的分配和释放。而创建和销毁进程的开销较大,需分配和回收资源,包括内存空间、文件描述符等。
  5. 安全性:由于线程共享进程的资源,多个线程访问共享数据时需要进行同步和互斥操作,以避免竞态条件等问题。而进程之间相互独立,相对较容易实现资源的隔离和保护。

3、线程相关函数:

1)、pthread_create函数

用于创建新线程,并让新线程开始执行指定的任务。。新线程的执行从 start_routine 函数开始,该函数在新线程中被调用,它接受一个参数 arg,可以将需要传递给线程的数据通过 arg 参数传递进去。

int pthread_create(pthread_t *__newthread, const pthread_attr_t * __attr, void *(*__start_routine)(void *), void * __arg)
//<pthread.h>

函数参数:

  • __newthread:线程号:返回的是指针,用于存储新创建线程的标识符;

  • __attr:设置线程访问控制对齐方式的结构体,通常为NULL表示使用默认属性;t.__align 设置对齐方式

  • void * (*__start_routine) (void *) :线程函数指针,回调函数

  • __arg :回调函数参数

函数返回值:

创建成功返回0;失败返回一个非零的错误代码,用于指示错误类型。

  • 创建线程实例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *Print1(void *arg)
{
    int *ptr = (int *)arg;//void*型赋值需要强制转换
    while(1)
    {
        (*ptr)++;
        printf("I am No.1 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
void *Print2(void *arg)
{
    int *ptr = (int *)arg;//void*型赋值需要强制转换
    while(1)
    {
        (*ptr)++;
        printf("I am No.2 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
int main()
{
    int num = 10;
    pthread_t t1,t2;
    pthread_create(&t1,NULL,Print1,&num);//创建线程并执行print1
    pthread_create(&t2,NULL,Print2,&num);//创建线程并执行print2  
    while(1)
    {
        printf("num : %d\n",num);
        sleep(2);
    }  
    while(1);
    return 0;
}

运行结果:
在这里插入图片描述

2、pthread_join函数

等待一个线程结束并回收线程资源获取其返回值

int pthread_join(pthread_t thread, void **retval);

函数参数:

  • thread:用于指定接收哪个线程的返回值
  • retval:表示接收到的返回值,如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,可以将 retval 参数置为 NULL

函数返回值:

如果成功等待到目标线程,返回 0,表示成功。失败,则返回一个非零的错误代码,用于指示错误类型。

注意:pthread_join() 函数会一直阻塞调用它的线程,直至目标线程执行结束(接收到目标线程的返回值),阻塞状态才会解除。如果目标线程已经结束,那么 pthread_join() 函数会立即返回,并将目标线程的返回值存储在 retval 指向的内存地址中。目标线程的返回值是一个 void* 类型的指针,需要在调用时传入一个 void** 类型的指针的地址来接收这个返回值。

  • pthread_join应用实例
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *Print1(void *arg)
{
    int *ptr = (int *)arg;//void*型赋值需要强制转换
    while(1)
    {
        (*ptr)++;
        printf("I am No.1 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
void *Print2(void *arg)
{
    int *ptr = (int *)arg;//void*型赋值需要强制转换
    while(1)
    {
        (*ptr)++;
        printf("I am No.2 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
int main()
{
    pthread_t t1,t2;
    pthread_create(&t1,NULL,Print1,&num);
    pthread_create(&t2,NULL,Print2,&num);
    while(1);
    void *value1;
    void *value2;
    pthread_join(t1,&value1);
    pthread_join(t2,&value2);
    printf("Thread 1 return : %d\n",*((int*)value1));
    printf("Thread 2 return : %d\n",*((int*)value2));
    return 0;
}

3、pthread_detach函数

分离线程:让线程独自运行,结束后被系统自动回收

int pthread_detach(pthread_t thread);

函数参数:

  • thread参数表示要设定状态的目标线程ID

函数返回值:

函数调用成功时返回0,失败则返回一个非零的错误码。

  • 函数应用实例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *Print1(void *arg)
{
    int *ptr = (int *)arg;
    while(1)
    {
        (*ptr)++;
        printf("I am No.1 thread! num : %d\n",*ptr);
        sleep(2);
    }
}

void *Print2(void *arg)
{
    int *ptr = (int *)arg;
    while(1)
    {
        (*ptr)++;
        printf("I am No.2 thread! num : %d\n",*ptr);
        sleep(2);
    }
}

int main()
{
    int num = 10;
    pthread_t t1,t2;
    pthread_create(&t1,NULL,Print1,&num);
    pthread_create(&t2,NULL,Print2,&num);
    
    pthread_detach(t1);//线程分离5秒后自动回收
    pthread_detach(t2);//线程分离5秒后自动回收
    sleep(5);
    return 0;
}

4、pthread_exit()函数

退出线程:返回值为它的参数;线程资源并不会被释放

void pthread_exit(void* retval);

函数参数:

  • retval参数是一个指针,表示线程的退出状态。可以将任意类型的值通过类型转换后传递给retval,父线程可以通过pthread_join()函数获取到这个退出状态。

5、pthread_cancel()函数

用于向指定线程发送取消请求,来取消线程,被取消的线程可以选择在适当的时机通过设置取消状态来终止自身。

int pthread_cancel(pthread_t thread);

函数参数:

  • thread:要取消的线程的标识符pthread_t

注意:线程取消是由其他线程发出的取消请求,目标线程可以选择在适当的时机进行取消处理;而线程退出是线程自身决定的操作,在执行完成或者显式调用pthread_exit()函数时终止自身。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void *Print1(void *arg)
{
    int *ptr = (int *)arg;
    while(1)
    {
        (*ptr)++;
        printf("I am No.1 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
void *Print2(void *arg)
{
    int *ptr = (int *)arg;
    while(1)
    {
        (*ptr)++;
        printf("I am No.2 thread! num : %d\n",*ptr);
        sleep(2);
    }
}
int main()
{
    int num = 10;
    pthread_t t1,t2;
    pthread_create(&t1,NULL,Print1,&num);
    pthread_create(&t2,NULL,Print2,&num);
    
    sleep(5);
    printf("ready to cancel thread 1\n");
    pthread_cancel(t1);//取消线程1
    
    while(1);
    return 0;
}

运行结果:

在这里插入图片描述

6、pthread_setcancelstate函数

设定线程取消状态的函数,可以用来控制线程是否可以被取消。

int pthread_setcancelstate(int __state, int *__oldstate)

设定某些线程不允许被取消:int pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL)

函数参数:

  • state:表示要设置的线程取消状态,可以取以下两个值之一:

    • PTHREAD_CANCEL_ENABLE:允许线程被取消。
    • PTHREAD_CANCEL_DISABLE:禁止线程被取消。
  • oldstate:是一个指向整型变量的指针,用于存储原来的线程取消状态。

函数返回值:成功返回0;失败返回一个非零的错误代码。

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

void* thread_func(void* arg) {
    // 设置线程取消状态为 PTHREAD_CANCEL_DISABLE
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    printf("Thread is running...\n");
    // 模拟耗时操作
    for (int i = 0; i < 10; i++) {
        printf("Working...%d\n", i);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_func, NULL);
    // 等待线程执行一段时间后,尝试取消线程
    sleep(3);
    pthread_cancel(thread);
    pthread_join(thread, NULL);
    printf("Thread has been canceled.\n");
    return 0;
}

运行结果:

在这里插入图片描述

7、pthread_cleanup_push函数和pthread_cleanup_pop函数

注册线程清理处理函数,允许在线程退出或被取消的时候执行一些清理操作。

void pthread_cleanup_push(void (*routine)(void*), void *arg);   

函数参数:

  • routine:一个函数指针,指向一个清理处理函数。该函数没有返回值

  • arg:是一个可选参数,传递给清理处理函数的参数。

在线程执行期间,当调用了pthread_exitpthread_cancel或由其它线程调用了pthread_kill来终止该线程时,注册的清理处理函数将被执行。

void pthread_cleanup_pop(int execute);

函数参数:

  • execute:不为0,执行清理函数;为0,不执行清理函数
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void ClearUp(void *arg)
{
    free((char *)arg);
    printf("clean up str!\n");
}
void *Print1(void *arg)
{
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
    int *ptr = (int *)arg;
    char *str = (char *)malloc(100);
    pthread_cleanup_push(ClearUp,str);
    for(int i = 0 ;i < 5 ; i++)
    {
        (*ptr)++;
        printf("I am No.1 thread! num : %d\n",*ptr);
        sleep(2);
    }
    pthread_exit(1);
    pthread_cleanup_pop(1);
}
int main()
{
    int num = 10;
    pthread_t t1;
    pthread_create(&t1,NULL,Print1,&num);
    void *value;
    pthread_join(t1,&value);
    
    return 0;
}

以下三种情况都会调用清理函数:

1、线程正常退出,由父线程进行回收之后

2、线程被取消

3、pthread_exit(1);自己退出

2、线程同步

线程同步:两个或两个以上的线程运行时,对同一资源,进行预定的先后顺序的访问

3、线程互斥

1)、线程互斥:同一时间,同一个资源只能由一个线程去访问

2)、互斥锁(又名互斥量)强调的是资源之间的访问互斥:每个线程在对共享资源操作前都会尝试先加锁,加锁成功才能操作,操作结束之后解锁。

某个线程对互斥量加锁后,任何其他试图再对互斥量加锁的线程都将被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态。第一个变成运行状态的线程可以对互斥量加锁,其余线程将会看到互斥量依然被锁住,只能回去再次等待它重新变为可用。

【1】、锁的初始化

1、动态初始化:使用函数上锁

int pthread_mutex_init(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr)

函数参数:

  • mutex:互斥锁变量的指针,需要提前定义好。

  • attr:指定了新建互斥锁的属性。如果参数attr为空(NULL),则使用默认的互斥锁属性,默认属性为快速互斥锁 。

函数返回值:

执行成功后都返回 0,否则返回错误编号以指名错误。

2、静态初始化:使用宏定义

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

【2】、锁的使用

pthread_mutex_lock(&mutex);		//上锁
pthread_mutex_unlock(&mutex);	//解锁

【3】、销毁锁

pthread_mutex_destroy(&mutex);

3)、死锁:多个线程因为争夺资源而造成的互相等待的局面

【1】、死锁产生的必要条件:

  • 互斥条件
  • 不剥夺条件:一个线程不能抢夺其他进程占有的进程,线程在获取到资源时候不会主动放弃或者被剥夺
  • 请求并保持条件:申请新资源的时候不释放自己已经占有的资源
  • 循环等待:存在一组资源循环等待资源的

四个条件满足不一定形成死锁;但是不满足一个则一定不会产生死锁

【2】、解决死锁的办法:

  • 避免死锁:加锁的顺序保持一致;
  • 在加锁的过程中尝试上锁pthread_mutex_trylock ;在加锁的状态里设置一个时间限制,十分钟上锁(加锁时限
  • 死锁检测:由上层分配资源,当发生死锁之后,结束所有锁的分配
#include "StdThread.h"
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int money = 10000;

void* zhangsan(void *arg)
{
    pthread_mutex_lock(&mutex);
    printf("谢广坤当前账户余额为: %d\n",money);

    int MyMoney = money;
    printf("一天,谢广坤想要取钱 !\n");
    sleep(1);

    if(MyMoney >= 10000)
    {
        money = 0;
        printf("谢广坤取了一万块钱,当前账户余额为: 0\n");
    }
    else
    {
        printf("谢广坤账户已经没有钱了!\n");
    }
    pthread_mutex_unlock(&mutex);
}

void* lisi(void *arg)
{
    pthread_mutex_lock(&mutex);
    printf("赵四当前账户余额为: %d\n",money);

    int MyMoney = money;
    printf("一天,赵四想要取钱 !\n");
    sleep(1);

    if(MyMoney >= 10000)
    {
        money = 0;
        printf("赵四取了一万块钱,当前账户余额为: 0\n");
    }
    else
    {
        printf("赵四账户已经没有钱了!\n");
    }
    pthread_mutex_unlock(&mutex);
}

int main()
{
    //pthread_mutex_init();
    Thread *t1 = InitThread(zhangsan,NULL);
    Thread *t2 = InitThread(lisi,NULL);
    JoinThread(t1);
    JoinThread(t2);

    pthread_mutex_destroy(&mutex);
    return 0;
}

执行结果:

在这里插入图片描述

4、条件变量

先将锁放开,等到信号到达时重新上锁

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用

1、条件变量的初始化

静态初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态初始化:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

函数参数:

  • cond:指向条件变量的指针,需要在调用函数之前定义好。条件变量是一种线程同步的机制,用于在线程之间等待和通知特定条件的发生。
  • attr:指向pthread_condattr_t类型对象的指针,表示条件变量的属性。如果希望使用默认属性,则可将此参数设置为NULL

执行成功后都返回 0,否则返回错误编号以指名错误。

2、条件变量的等待:pthread_cond_waitpthread_cond_timedwait显示等待

pthread_cond_wait 函数具体执行过程:

【1】、释放锁

【2】、条件变量会阻塞

【3】、当信号来临,锁被重新上锁,继续执行

int pthread_cond_wait(pthread_cond_t *__cond, pthread_mutex_t *__mutex)

函数参数:

  • cond:指向条件变量的指针,需要在调用函数之前定义好;
  • mutex:指向互斥锁的指针,用于保护共享资源;
int pthread_cond_timedwait(pthread_cond_t *__cond, pthread_mutex_t *__restrict__ __mutex, const struct timespec *__restrict__ __abstime)

函数是一个具有超时功能的条件变量等待函数,用于使线程在指定时间内等待条件变量满足特定条件。

  • cond:指向条件变量的指针,需要在调用函数之前定义好。
  • mutex:指向互斥锁的指针,用于保护共享资源。
  • abstime:指向结构体timespec的指针,用于指定等待条件的截止时间。

3、发出条件变量信号:

int pthread_cond_signal(pthread_cond_t *__cond)

4、销毁条件变量

int pthread_cond_destroy(pthread_cond_t *__cond)
#include "StdThread.h"
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int flag = 0;
void* Print1(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(flag == 0)
        {
            printf("A抢到了\n");
            sleep(2);
            flag = 1;
            pthread_cond_signal(&cond);
        }
        else
        {
            pthread_cond_wait(&cond,&mutex);
        }
        pthread_mutex_unlock(&mutex);
    }
}

void* Print2(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(flag == 1)
        {
            printf("B抢到了\n");
            sleep(2);
            flag = 0;
            pthread_cond_signal(&cond);
        }
        else
        {
            pthread_cond_wait(&cond,&mutex);
        }
        pthread_mutex_unlock(&mutex);
    }
}
    

int main()
{
    Thread *t1 = InitThread(Print1,NULL);
    Thread *t2 = InitThread(Print2,NULL);
    JoinThread(t1);
    JoinThread(t2);
    while(1);
    return 0;
}
5、信号量:

在线程的时候能够判别优先级

PV p---->-1

​ v---->+1

1、信号量初始化

int sem_init(sem_t *__sem, int __pshared, unsigned int __value)
#include<semaphere.h>

函数参数:

  • __sem :返回信号量结构体

  • __pshared :信号所用的范围:1—>进程共享 0—>线程共享

    • pshared为0时,表示信号量只能用于同一进程内的线程间同步。

    • pshared为非零值时,表示信号量可用于多个进程间的线程同步。

  • __value:信号量的初始值

函数返回值:

如果返回值为0,表示初始化信号量成功。如果返回值为-1,表示初始化信号量失败,可能是由于参数错误或者系统资源不足。

2、信号量p操作

  1. 当线程调用sem_wait时,它首先会检查信号量的值是否大于0。
  2. 如果信号量的值大于0,则线程将继续执行后续代码,且信号量的值减1。
  3. 如果信号量的值为0,则线程将进入等待状态,直到有其他线程调用了相应信号量的sem_post函数,使信号量的值增加,并且等待的线程被唤醒。
  4. 当线程被唤醒后,再次重新检查信号量的值,如果大于0,则继续执行后续代码。
int sem_wait(sem_t *__sem)
int sem_trywait(sem_t *__sem)

3、信号量v操作

  1. 当线程调用sem_post时,它会增加信号量的值。
  2. 如果有其他线程正在等待该信号量,其中一个线程将被激活(唤醒),从而可以继续执行后续代码。
  3. 如果没有其他线程正在等待该信号量,那么信号量的值仅简单地增加。
int sem_post(sem_t *__sem)

4、信号量销毁

  1. 当调用sem_destroy函数时,它会检查信号量是否已经被初始化。
  2. 如果信号量尚未初始化或者已经被销毁,则该函数不会进行任何操作。
  3. 否则,该函数会释放信号量的相关资源,并将信号量置于未初始化的状态。
int sem_destroy(sem_t *sem);
  • 信号量应用实例:
#include "StdThread.h"
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem[3];
void* Print1(void *arg)
{
    while(1)
    {
        sem_wait(&sem[0]);
        printf("A\n");
        sleep(1);
        sem_post(&sem[1]);
    }
}

void* Print2(void *arg)
{
    while(1)
    {
        sem_wait(&sem[1]);
        printf("B\n");
        sleep(1);
        sem_post(&sem[2]);
    }
}

void* Print3(void *arg)
{
    while(1)
    {
        sem_wait(&sem[2]);
        printf("C\n");
        sleep(1);
        sem_post(&sem[0]);
    }
}    

int main()
{
    sem_init(&sem[0],0,1);//let A first
    sem_init(&sem[1],0,0);
    sem_init(&sem[2],0,0);

    Thread *t1 = InitThread(Print1,NULL);
    Thread *t2 = InitThread(Print2,NULL);
    Thread *t3 = InitThread(Print3,NULL);
    JoinThread(t1);
    JoinThread(t2);
    JoinThread(t3);

    for(int i=0; i < 3 ;i++)
    {
        sem_destroy(&sem[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值