在上一篇关于Linux系统编程中IPC对象--信号量与PV操作后。在这篇博客文章开始,正式进入了对Linux系统编程部分的多线程部分的内容进行讲解。在这一部分,将会先讲解关于Linux多线程的基本API接口介绍,然后通过程序代码实现:多线程 + 消息队列 实现 两个进程的互聊。在之后将会由浅入深的讲解梳理Linux多线程中的其它知识点,如互斥锁读写锁条件变量线程池

        如有博客朋友需要或者感兴趣的,欢迎访问博主的Linu系统编程专栏内容进行参考交流学习。

系统编程_奔跑的蜗牛!的博客-CSDN博客https://blog.csdn.net/weixin_49337111/category_12952742.html?spm=1001.2014.3001.5482

1、线程基础

(1)、线程的概念

        线程实际上是应用层的概念。在Linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。

  • 左边是一个含有单个线程的进程,它拥有自己的一套完整的资源。
  • 右边是一个含有两条线程的进程,线程彼此间共享进程内的资源。

        由此可见,线程是一种轻量级进程,提供一种高效的任务处理方式。

进程:进程是操作系统资源分配的基本实体。

线程:线程是CPU调度基本单位,是进程内部资源,是linux中最小资源单位

        用个不太正确的形象化描述就是:假设linux系统是一个社会,那么进程就是一个人,线程就是这个人的一只手。

(2)、线程的特点

        资源共享:同一进程内的所有线程共享该进程的资源,包括内存地址空间、文件描述符等。这使得线程之间的数据交换变得高效。

        并发性:线程最重要的特性是并发,线程函数 doSomething() 会与主线程 main() 同时运行,这是它与普通函数调用的根本区别。需要特别提醒的是,由于线程函数的并发性,在线程中访问共享资源(堆、栈、数据段...)需要特别小心,因为这些共享资源会被多个线程争抢,形成“竞态”。

        轻量级:相比于进程,线程是一种更为轻量级的执行单元。线程的创建、销毁以及上下文切换的成本通常低于进程,因此可以更有效地利用系统资源。

2、多线程常用API接口

(1)、线程创建

        创建一条POSIX线程非常简单,只需指定线程的执行函数即可。

函数原型:
#include <pthread.h>

int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine) (void *),
                   void *arg);
参数说明:
    thread:新线程的TID(线程号)
    attr:线程属性,若创建标准线程则该参数可设置为NULL
    start_routine:线程函数
    arg:线程函数的参数
返回值:
    成功:返回 0 
    失败:直接返回错误码

(2)、线程退出

        与进程的操作类似,当一条线程执行完毕其任务时,需要结束当前线程时,可以使用如下接口来退出线程:

#include <pthread.h>

void pthread_exit(void *retval);

Compile and link with -pthread.

        上述接口中,参数retval是线程的返回值,对应线程执行函数的返回值。若线程没有数据可返回则可写成NULL。

        注意pthread_exit函数与exit的区别:

  • pthread_exit(): 退出当前线程
  • exit():退出当前进程(即退出进程中的所有线程

        一个进程中各个线程是平行并发运行的,运行主函数main()的线程被称为主线程,主线程是可以比其他线程先退出的。

        主线程退出后,其余线程可以继续运行,但需注意,上述代码中如果主线程不调用 pthread_exit() 的话,那么相当于退出了整个进程,则子线程也会被迫退出

(3)、线程等待

        与进程类似,线程退出之后不会立即释放其所占有的系统资源,而会成为一个僵尸线程。其他线程可使用 pthread_join() 来释放僵尸线程的资源,并可获得其退出时返回的退出值,该接口函数被称为线程的等待函数,也称为接合函数:

#include <pthread.h>

int pthread_join(pthread_t tid, void **val);
参数分析:
    tid --> 需要阻塞等待的线程ID 
    val --> 需要接合线程的线程退出值 

返回值:
    成功返回 0 
    失败直接返回错误码

        接口说明:

  • 若指定tid的线程尚未退出,那么pthread_join函数将持续阻塞等待。
  • 若只想阻塞等待指定线程tid退出,而不想要其退出值,那么val可置为NULL。
  • 若指定tid的线程处于分离状态,或不存在,则该函数会出错返回。

(4)、线程分离

        线程默认是可接合的(joinable),如果没有对一个 joinable 线程进行(pthread_join)处理,那么该线程结束后会留下一些资源(如线程栈、线程描述符等)未被回收,造成“僵尸线程”。

        因此将线程设置为 分离状态(detached),一旦线程变为分离状态,它在结束时会自动释放所有资源,无需其他线程调用 pthread_join()。

#include <pthread.h>

int pthread_detach(pthread_t thread);

Compile and link with -pthread.

(5)、线程取消

        用于取消(终止)一个正在运行的线程。pthread_cancel() 函数允许一个线程请求另一个线程提前结束执行。这与直接退出或返回不同,它是一种“异步”地请求终止目标线程的方式

#include <pthread.h>

int pthread_cancel(pthread_t thread);

Compile and link with -pthread.

        调用 pthread_cancel() 并不会立即终止目标线程,而是发送一个“取消请求”,目标线程在适当的时机响应这个请求并退出。

(6)、获取线程号

        在Linux环境下获取线程标识符的两种主要方式:gettid() 和 pthread_self()

        gettid:用于获取调用线程的真实线程ID(TID)。TID是系统范围内唯一的,但在某些情况下,特别是在较旧的Linux内核版本中,可能需要定义 _GNU_SOURCE 才能使用此函数。

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>

pid_t gettid(void);

        pthread_self:返回调用线程的线程标识符,类型为 pthread_t。与 gettid() 不同,这个标识符主要用于在程序内部识别线程,比如对线程进行取消操作、设置线程属性等。它不是系统全局唯一的,仅在进程内部有意义。

#include <pthread.h>

pthread_t pthread_self(void);

Compile and link with -pthread.

(7)、线程ID比较

        用于比较两个线程 ID 是否相等。

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

Compile and link with -pthread.

        在某些系统实现中,pthread_t 类型可能并不是简单的整数,而是一个结构体或指针类型,因此不能直接使用 == 运算符进行比较。

        为了保证可移植性,POSIX 标准推荐使用 pthread_equal() 函数来判断两个线程 ID 是否相等。

3、多线程应用举例

        应用案例:使用 多线程 + 消息队列 实现 两个进程的互聊

(1)、多线程代码注意事项

        多线程的程序代码,在编译的时候增加链接线程库即可 -lpthread

        注意:Compile and link with -pthread. 只要程序代码设计中用到了线程的函数,在编译时必须链接线程库。

        编译命令: gcc xxx.c -o xxx -lpthread

        在某些系统中,如果没有加-lpthread 进行编译时,可能会报错,因此建议都加上。

(2)、进程1

#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>

struct msgbuf{
    long mtype;
    char mtext[1024];
};

void * receive(void *arg)
{
    //获取消息队列的ID号
    int msgid = *((int*)arg);
    
    while(1)
    {
        struct msgbuf data;
        memset(&data,0,sizeof(data));
        
        data.mtype = 20;
        scanf("%s",data.mtext);
        
        msgsnd(msgid,&data,strlen(data.mtext),0);
        
        if(!strcmp(data.mtext,"exit"))
            exit(0);
    }
}

int main()
{
    int regc;
    pthread_t pthread;
    
    //1、先申请key值
    key_t key = ftok(".",10);
    //2、获取消息队列的ID号 如果消息队列不存在则创建
    int msgid = msgget(key,IPC_CREAT|0666);
    if(msgid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("key:%#x,msgid:%d\n",key,msgid);
    
    //创建子线程 用来发送数据
    regc = pthread_create(&pthread,NULL,receive,&msgid);
    if(regc != 0)
    {
        perror("pthread_create error");
        return -1;
    }
        
    struct msgbuf data;
    while(1)
    {
        memset(&data,0,sizeof(data));
        
        msgrcv(msgid,&data,sizeof(data.mtext),10,0);
        printf("接收到消息:%s\n",data.mtext);
        
        if(!strcmp(data.mtext,"exit"))
            exit(0);
    }
}

(3)、进程2

#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>

struct msgbuf{
    long mtype;
    char mtext[1024];
};

void * receive(void *arg)
{
    //获取消息队列的ID号
    int msgid = *((int*)arg);
    
    while(1)
    {
        struct msgbuf data;
        memset(&data,0,sizeof(data));
        
        data.mtype = 10;
        scanf("%s",data.mtext);
        
        msgsnd(msgid,&data,strlen(data.mtext),0);
        
        if(!strcmp(data.mtext,"exit"))
            exit(0);
    }
}

int main()
{
    int regc;
    pthread_t pthread;
    
    //1、先申请key值
    key_t key = ftok(".",10);
    //2、获取消息队列的ID号 如果消息队列不存在则创建
    int msgid = msgget(key,IPC_CREAT|0666);
    if(msgid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("key:%#x,msgid:%d\n",key,msgid);
    
    //创建子线程 用来发送数据
    regc = pthread_create(&pthread,NULL,receive,&msgid);
    if(regc != 0)
    {
        perror("pthread_create error");
        return -1;
    }
            
    struct msgbuf data;
    while(1)
    {
        memset(&data,0,sizeof(data));
        
        msgrcv(msgid,&data,sizeof(data.mtext),20,0);
        printf("接收到消息:%s\n",data.mtext);
        
        if(!strcmp(data.mtext,"exit"))
            exit(0);
    }
}

多线程 + 消息队列 实现 两个进程的互聊的结果图。

        多线程 + 消息队列 实现 两个进程的互聊的结果图。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐