Linux系统编程 | 多线程---CPU调度最小单位
线程实际上是应用层的概念。在Linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。进程: 进程是操作系统资源分配的基本实体。线程: 线程是CPU调度的基本单位,是进程内部资源,是linux中最小资源单位。资源共享:同一进程内的所有线程共享该进程的资源,包括内存地址空间、文件描述符等。这使得线程之间的数据交换变得高
在上一篇关于Linux系统编程中IPC对象--信号量与PV操作后。在这篇博客文章开始,正式进入了对Linux系统编程部分的多线程部分的内容进行讲解。在这一部分,将会先讲解关于Linux多线程的基本API接口介绍,然后通过程序代码实现:多线程 + 消息队列 实现 两个进程的互聊。在之后将会由浅入深的讲解梳理Linux多线程中的其它知识点,如互斥锁、读写锁、条件变量和线程池。
如有博客朋友需要或者感兴趣的,欢迎访问博主的Linu系统编程专栏内容进行参考交流学习。
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);
}
}
多线程 + 消息队列 实现 两个进程的互聊的结果图。
多线程 + 消息队列 实现 两个进程的互聊的结果图。
更多推荐
所有评论(0)