当前位置: 首页 > java >正文

Linux线程

目录

一、基本知识

线程与进程的区别

使用线程的理由(与进程相比)

二、常用API

线程创建

函数原型及头文件

参数

返回值

线程退出

函数原型及头文件

参数

线程等待

函数原型及头文件

参数

返回值

线程脱离

函数原型及头文件

参数

返回值

其他

线程ID获取

函数原型及头文件

返回值

线程比较

函数原型及头文件

参数

返回值

代码示例

三、互斥锁

概念

常用API

包含的头文件

创建互斥锁

函数原型

参数

返回值

销毁互斥锁

函数原型

参数

返回值

加锁互斥锁

函数原型

参数

返回值

解锁互斥锁

函数原型

参数解读

返回值

代码示例

四、条件变量

什么是条件变量

相关API

包含头文件

创建及销毁条件变量

等待条件变量

触发条件变量

代码示例


一、基本知识

线程与进程的区别

典型的UNIX/Linux进程可以看成只有一个控制线程: 一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

进程
进程是程席执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程 (kernel thread),而把用户线程 (user thread) 称为线程。

进程一一资源分配的最小单位        线程一一程序执行的最小单位

使用线程的理由(与进程相比)

从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间 (同一进程内的线程共享进程的地址空间)。

一、一种非常"节俭"的多任务操作方式
我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

二、线程间方便的通信机制
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

线程的优点
多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

1、提高应用程序响应
这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming) 置于一个新的线程,可以避免这种临尬的情况。

2、使多CPU系统更加有效
操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

3、改善程序结构
一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

线程常用开发API概要
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。
其涉及的多线程开发的最基本概念主要包含三点:1、线程,2、互斥锁,3、条件。

线程操作
线程操作包括3 种:创建,退出,等待。

互斥锁
互斥锁则包括 4 种操作,分别是:创建,销毁,加锁和解锁。

条件操作
条件操作有 5 种操作:创建,销毁,触发,广播和等待。

其他
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:

二、常用API

线程创建

函数原型及头文件
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
参数

 tidp:当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。(指针,输入方式是地址)

attr:一般设为NULL。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。

(*start_rtn)(void *):新创建的线程的入口地址。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。

arg:向start_rtn函数传递的参数。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。(指针,输入方式是地址,同时要注意是void *型)

返回值

若成功返回0,否则返回错误编号。

线程退出

单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

1)线程只是从启动例程中返回,返回值是线程的退出码。

2)线程可以被同一进程中的其他线程取消。

3)线程调用pthread_exit。

函数原型及头文件
#include <pthread.h>
int pthread_exit(void *rval_ptr);
参数

rval_ptr:一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

 

线程等待

调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。

可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。

函数原型及头文件
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
参数

thread:线程名

**rval_ptr:线程的返回值。如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。

返回值

若成功返回0,否则返回错误编号。

线程脱离

函数原型及头文件
#include <pthread.h>
int pthread_detach(pthread_t thread);
参数

一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。脱离的线程却像守护进程,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态。

pthread_detach函数把指定的线程转变为脱离状态。(就是不让他被等待,脱离出来)

返回值

若成功返回0,否则返回错误编号。

其他

本函数通常由想让自己脱离的线程使用,就如以下语句:

pthread_detach(pthread_self());

 

线程ID获取

函数原型及头文件
#include <pthread.h>
pthread_t pthread_self(void);
返回值

调用线程的ID。

线程比较

对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。

函数原型及头文件
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
参数

tid1:线程1的名字

tid2:线程2的名字

返回值

若相等则返回非0值,否则返回0。

代码示例

线程返回数字打印

#include <stdio.h>
#include <pthread.h>void *func1(void *arg)
{static int ret = 1;//static保留数据 不然函数调用后空间无效printf("t1:this thread t1:%ld \n",(unsigned long)pthread_self());//self是void型 需对其进行强转printf("t1:num=%d\n",*((int *)arg));//传参过来的是主函数的num num取地址传过来需先将void型转成int型 再通过指针指向其地址得到数据pthread_exit((void *)&ret);//线程的退出 可附带线程的返回值 可通过pthread_join调用得到该数据
}
int main()
{int ret;int num =999;int *pret;pthread_t t1;ret = pthread_create(&t1,NULL,func1,(void *)&num);//第四个参数是void *型 而num是int型 需要对其进行强转 同时其输出为指针 则需要加上地址符号if(ret == 0)//判断线程是否创建成功{printf("main:create thread success\n");}printf("main:%ld\n",(unsigned long)pthread_self());	pthread_join(t1,(void **)&pret);//线程等待 等待上面函数退出之后再执行下面语句printf("main:ti quit %d\n",*pret);//打印出线程的返回值return 0;
}

                        

线程返回字符串打印

#include <stdio.h>
#include <pthread.h>void *func1(void *arg)
{static char *p = "hello word";printf("t1:this thread t1:%ld \n",(unsigned long)pthread_self());printf("t1:num=%d\n",*((int *)arg));pthread_exit((void *)p);
}
int main()
{int ret;int num =999;char *pret;pthread_t t1;ret = pthread_create(&t1,NULL,func1,(void *)&num);if(ret == 0){printf("main:create thread success\n");}printf("main:%ld\n",(unsigned long)pthread_self());	pthread_join(t1,(void **)&pret);printf("main:ti quit %s\n",pret);//字符串的名字为地址 不需要加地址符return 0;
}

线程共享内存空间

#include <stdio.h>
#include <pthread.h>int data = 0;void *func1(void *arg)
{printf("t1:this thread t1:%ld \n",(unsigned long)pthread_self());printf("t1:num=%d\n",*((int *)arg));while(1){printf("t1:%d\n",data++);sleep(1);}
}void *func2(void *arg)
{printf("t2:this thread t2:%ld \n",(unsigned long)pthread_self());printf("t2:num=%d\n",*((int *)arg));while(1){printf("t2:%d\n",data++);sleep(1);}}int main()
{int ret;int num =999;pthread_t t1;pthread_t t2;ret = pthread_create(&t1,NULL,func1,(void *)&num);if(ret == 0){printf("main:create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void *)&num);if(ret == 0){printf("main:create t2 success\n");}printf("main:%ld\n",(unsigned long)pthread_self());	while(1){printf("main:%d\n",data++);sleep(1);}pthread_join(t1,NULL);pthread_join(t2,NULL);	return 0;
}

                

上面两个线程都有自己独立的栈,但是他们的值却是共享++的,所以我们的线程是共享内存空间的

三、互斥锁

概念

互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。

如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。

在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。

互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。

常用API

包含的头文件

#include <pthread.h>

 

创建互斥锁

函数原型
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数

*mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量。

attr:互斥量的属性,一般设为NULL,要用默认的属性初始化互斥量,只需把attr设置为NULL。

返回值

若成功返回0,否则返回错误编号。

销毁互斥锁

函数原型
int pthread_mutex_destroy(pthread_mutex_t mutex);
参数

*mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量。

返回值

若成功返回0,否则返回错误编号。

加锁互斥锁

函数原型
int pthread_mutex_lock(pthread_mutex_t mutex);
参数

*mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量。

返回值

若成功返回0,否则返回错误编号。

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。

解锁互斥锁

函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数解读

*mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量。

返回值

若成功返回0,否则返回错误编号。

代码示例

互斥锁的创建及加锁解锁

#include <stdio.h>
#include <pthread.h>int data = 0;pthread_mutex_t mutex;//定义一个互斥锁void *func1(void *arg)
{int i;pthread_mutex_lock(&mutex);//上锁 执行到该锁时会进行其中代码 直到解锁后才会进行其他代码for(i=0;i<5;i++){printf("t1:this thread t1:%ld \n",(unsigned long)pthread_self());printf("t1:num=%d\n",*((int *)arg));}pthread_mutex_unlock(&mutex);//解锁}void *func2(void *arg)
{pthread_mutex_lock(&mutex);printf("t2:this thread t2:%ld \n",(unsigned long)pthread_self());printf("t2:num=%d\n",*((int *)arg));pthread_mutex_unlock(&mutex);
}int main()
{int ret;int num =999;pthread_t t1;pthread_t t2;pthread_mutex_init(&mutex,NULL);//初始化互斥锁ret = pthread_create(&t1,NULL,func1,(void *)&num);if(ret == 0){printf("main:create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void *)&num);if(ret == 0){printf("main:create t2 success\n");}printf("main:%ld\n",(unsigned long)pthread_self());	pthread_join(t1,NULL);pthread_join(t2,NULL);	pthread_mutex_destroy(&mutex);//销毁互斥锁return 0;
}

                        

由编译结果可知:t1会被main函数打断,但是不会被t2,t3打断,t1运行后t2,t3才能运行。

互斥锁限制共享资源的访问

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>int data = 0;pthread_mutex_t mutex;void *func1(void *arg)
{printf("t1:this thread t1:%ld \n",(unsigned long)pthread_self());printf("t1:num=%d\n",*((int *)arg));pthread_mutex_lock(&mutex); //上锁后执行下面语句while(1){printf("ti:%d\n",data++);sleep(1);if(data==3){pthread_mutex_unlock(&mutex);//解锁printf("over!!!!!!!!!!!!!!!!!!\n");//执行后结束func1线程//		pthread_exit(NULL);exit(0);//执行后结束整个进程}}
}void *func2(void *arg)
{printf("t2:this thread t2:%ld \n",(unsigned long)pthread_self());printf("t2:num=%d\n",*((int *)arg));while(1){printf("t2:%d\n",data);pthread_mutex_lock(&mutex);//func2进入检测锁的循环 判断func1是否解锁data++;pthread_mutex_unlock(&mutex);sleep(1);}
}int main()
{int ret;int num =999;pthread_t t1;pthread_t t2;pthread_mutex_init(&mutex,NULL);ret = pthread_create(&t1,NULL,func1,(void *)&num);if(ret == 0){printf("main:create thread success\n");}ret = pthread_create(&t2,NULL,func2,(void *)&num);if(ret == 0){printf("main:create thread success\n");}printf("main:%ld\n",(unsigned long)pthread_self());	while(1){printf("main:%d\n",data);sleep(1);}pthread_join(t1,NULL);pthread_join(t2,NULL);	pthread_mutex_destroy(&mutex);return 0;
}

                ​​​​​​​        

由编译结果可知,func1线程结束后整个进程结束。

四、条件变量

什么是条件变量

条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生

条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。

条件变量使用之前必须首先初始化

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量。

动态初始化:pthread_cond_init(&cond,NULL);如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化(deinitialize)。

相关API

包含头文件

#include <pthread.h>

 

创建及销毁条件变量

函数原型

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

参数

*cond:全局变量pthread_cond_t cond的地址(pthread_cond_init(&cond,NULL);)

attr:除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL(pthread_cond_destroy(&cond);)

返回值

若成功返回0,否则返回错误编号。

等待条件变量

pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。

pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

pthread_cond_wait立即返回,pthread_cond_wait等待一段时间后返回。

函数原型

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, cond struct timespec *restrict timeout);

参数解读

*cond:全局变量pthread_cond_t cond的地址

*restrict mutex:全局变量pthread_mutex_t mutex的地址

返回值

若成功返回0,否则返回错误编号。

触发条件变量

这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。

函数原型

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

参数解读

*cond:全局变量pthread_cond_t cond的地址。

返回值

若成功返回0,否则返回错误编号。

代码示例

线程条件控制实现线程的同步

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>int data = 0;pthread_mutex_t mutex;
pthread_cond_t cond;//定义一个全局变量的条件变量
//pthread_cond_t cond = PTHREAD_COND_INITIALIZER//这也是初始化的一种并定义一个全局变量void *func1(void *arg)
{while(1){pthread_cond_wait(&cond,&mutex);//等待func2触发条件变量printf("over!!!!!!!!!!!!!!!!!!\n");data = 0;//将data置于0 则data不等3会重新进入func2sleep(1);}
}void *func2(void *arg)
{while(1){printf("t2:data = %d\n",data);pthread_mutex_lock(&mutex);//上锁data++;if(data == 3){pthread_cond_signal(&cond);//触发条件变量 func1等待接受后执行func1代码}pthread_mutex_unlock(&mutex);//解锁sleep(1);}
}int main()
{int ret;int num =999;pthread_t t1;pthread_t t2;pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);//条件变量初始化ret = pthread_create(&t1,NULL,func1,(void *)&num);if(ret == 0){//printf("main:create thread success\n");}ret = pthread_create(&t2,NULL,func2,(void *)&num);if(ret == 0){//printf("main:create thread success\n");}pthread_join(t1,NULL);pthread_join(t2,NULL);	pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);//销毁条件变量return 0;
}

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

由编译结果可知:data不等于3时fucn2会拿到互斥量并执行其中代码,当data等于3时触发条件变量并解锁,此时func1接受条件变量并将data重新置0,data等于0后func2就会重新进行。

http://www.xdnf.cn/news/6419.html

相关文章:

  • 提高绳牵引并联连续体机器人运动学建模精度的基于Transformer的分段学习方法
  • homeassistant安装
  • 加密原理1
  • C#中的typeof操作符与Type类型:揭秘.NET反射的基础
  • AgenticSeek开源的完全本地的 Manus AI。无需 API,享受一个自主代理,它可以思考、浏览 Web 和编码,只需支付电费。
  • OpenSSH 漏洞-SSH 服务器面临 MitM 攻击和拒绝服务攻击的风险
  • 电路中零极点的含义
  • 学习黑客LAN与WAN详解-网络通信的局域与广域之旅
  • 九、HQL DQL七大查询子句
  • 开发日常中的抓包工具经验谈:Charles 抓包工具与其它选项对比
  • 解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs- Manus技术解密
  • 高可靠低纹波国产4644电源芯片在工业设备的应用
  • 通义千问-langchain使用构建(二)
  • pytorch中各种乘法操作
  • 英语六级听力
  • 嵌入式开发学习日志(数据结构--双链表)Day21
  • 全栈项目中是否可以实现统一错误处理链?如果可以,这条链路该如何设计?需要哪些技术支撑?是否能同时满足性能、安全性和用户体验需求?
  • OptiStruct实例:汽车声控建模
  • 零基础学Java——第十一章:实战项目 - 微服务入门
  • JAVA入门-三元运算符
  • 解读RTOS 第八篇 · 内核源码解读:以 FreeRTOS 为例
  • CPSE直击丨飞凌嵌入式亮相2025上海充换电展
  • 扣子(Coze)案例:工作流生成小红书心理学卡片
  • 自动驾驶传感器数据处理:Python 如何让无人车更智能?
  • 一个简单点的js的h5页面实现地铁快跑的小游戏
  • Spring AI(6)——向量存储
  • mongodb处理时区转换问题
  • 论云原生架构及其应用~系统架构师论文
  • 前端ECS简介
  • Java爬虫能处理京东商品数据吗?