Linux线程

概念

线程(Thread)也被叫做轻量级进程(Lightweight Process,LWP),它是程序执行流的最小单元。如果把进程看做成一个工厂的话,那么线程就是工厂里的工人,所以一个进程里能够包含多个线程,这些线程会共享进程的大部分资源,像内存空间、文件描述符等,不过每个线程都有自己独立的程序计数器、栈空间和寄存器组。线程能够并发执行,从而提升程序的性能与响应速度。

在 Linux 系统中,线程的实现依赖于内核调度器,内核会把线程当作独立的调度单元,按照调度算法来分配 CPU 时间片。线程之间的切换开销相对较小,因为它们共享进程的资源,不用像进程切换那样进行大量的上下文切换。

线程相关函数

线程的创建

主线程先退出,子线程会被强制结束

复制代码
#include <pthread.h>

int pthread_create( pthread_t *thread), //线程ID = 无符号长整型
                    const pthread_attr_t *attr, //线程属性
                    void *(*start_routine)(void *), //线程处理函数
                    void *arg); //线程处理函数
/*参数:
pthread:传出参数,线程创建成功之后,会被设置一个合适的值
attr:线程属性,可设置为NULL
start_routine:子线程的处理函数
arg: 处理函数的参数,void*类型,在传递前需要进行类型转换。在 start_routine 函数内部,需要将其转换回原来的类型才能使用。
返回值:
  成功:0
  错误:错误码 //perror不能使用该函数打印错误信息*/

单个线程退出

复制代码
#include <pthread.h>

void pthread_exit(void *retval);

/*参数说明

retval:这是一个 void * 类型的指针,用于传递线程的返回值,必须指向全局、堆,如果不需要返回任何值,可以将其设置为 NULL
返回值
该函数没有返回值,因为调用该函数的线程会立即终止执行。*/

阻塞等待线程退出

该函数阻塞等待线程退出,能够回收线程资源,避免资源泄漏;还可以获取线程的返回值,实现线程间的数据传递

复制代码
#include <pthread.h>

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

/*参数:
pthread:要回收的子线程的ID
void **retval:这是一个指向指针的指针,用于获取目标线程的返回值,不关心返回值可设置为NULL
(void* ptr;
 pthread_join(pthid,&ptr);
 指向的内存和pthread_exit参数指向地址一致)

返回值
如果函数调用成功,返回 0。
如果调用失败,返回一个非零的错误码*/

线程分离

调用该函数之后不需要 pthread_join
子线程会自动回收自己的 PCB
由于分离线程的资源会自动回收,主线程无法得知其确切的结束时间,因此在设计程序时需要考虑这一点

复制代码
#include <pthread.h>

int pthread_detach(pthread_t thread);

/*参数
  pthread_t thread:需要被设置为分离状态的线程的线程ID。

  返回值
   若函数调用成功,返回 0。
   若调用失败,返回一个非零的错误码*/

取消线程

向指定的 thread 线程发送一个取消请求,请求该线程终止执行

注意:

线程需要有取消点

线程不会在任意位置响应取消请求,它需要在特定的 "取消点" 才能响应。取消点通常是一些系统调用或库函数,这些函数在执行过程中会检查是否有取消请求,并根据线程的取消状态和类型决定是否终止线程。
系统调用作为取消点

像 write、read、printf 这类系统调用或库函数在执行时会检查取消请求,所以在线程函数中进行这些操作可以让线程有机会响应取消请求

手动设置取消点

如果线程函数中没有自然的取消点(如系统调用),可以使用 pthread_testcancel 函数手动设置取消点。

pthread_testcancel();//设置取消点

复制代码
#include <pthread.h>

int pthread_cancel(pthread_t thread);

/*参数
  pthread_t thread:要取消的线程的线程ID。

  返回值
   若函数调用成功,返回 0。
   若调用失败,返回一个非零的错误码*/

比较两个线程ID是否相等

复制代码
#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

/*参数
pthread_t t1:要进行比较的第一个线程的线程 ID。
pthread_t t2:要进行比较的第二个线程的线程 ID。
返回值
如果 t1 和 t2 代表同一个线程,函数返回一个非零值。
如果 t1 和 t2 代表不同的线程,函数返回 0。*/

strerror函数

复制代码
#include <string.h>

char *strerror(int errnum);

/*参数
int errnum:错误码
返回值:返回错误信息的字符串*/

线程的分离属性

通过属性设置线程的分离

处于分离状态的线程在结束执行后,系统会自动回收其占用的资源,因此不能再使用 pthread_join 函数等待该线程结束。

在使用线程属性对象时,要确保在不再使用时调用 pthread_attr_destroy 函数进行销毁,避免资源泄漏。

复制代码
pthread_attr_t attr;//线程属性类型 

int pthread_attr_init(pthread_attr_t* attr);//对线程属性变量的初始化

//设置线程分离属性
int pthread_attr_setdetachstate(
pthread_attr_t* attr,
int detachstate
);

/*参数:
attr : 线程属性
detachstate:
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)*/

//调用创建线程函数
pthread_create(); //传入初始化好的线程属性对象attr

//释放线程资源函数
int pthread_attr_destroy(pthread_attr_t* attr);

线程同步

当多个线程同时访问和修改共享资源时,可能会导致数据不一致或程序出现错误的结果。例如,两个线程同时对一个全局变量进行加一操作,如果没有适当的同步机制,可能会导致结果不准确。

线程同步是指多个线程在执行过程中,通过协调它们的执行顺序和访问共享资源的方式,以确保程序的正确性和稳定性。

线程同步的方法

**互斥锁(Mutex):**互斥锁是一种最基本的线程同步机制。它用于保护共享资源,确保在任何时刻只有一个线程能够访问该资源。线程在访问共享资源之前,需要先获取互斥锁,访问完成后释放互斥锁。例如,在 C 语言中使用 POSIX 线程库,可以通过 pthread_mutex_t 类型来定义互斥锁,使用 pthread_mutex_lock 函数获取锁,pthread_mutex_unlock 函数释放锁。

**信号量(Semaphore):**信号量用于控制对共享资源的访问数量。它维护一个计数器,线程在访问共享资源前需要先获取信号量,如果计数器大于零,则允许线程访问,并将计数器减一;如果计数器为零,则线程需要等待。信号量可以用于实现资源池、控制并发访问数量等场景。在 C 语言中,可以使用 sem_t 类型来定义信号量,通过 sem_wait 函数获取信号量,sem_post 函数释放信号量。

条件变量(Condition Variable): 条件变量用于在线程之间进行通信,它允许线程在满足特定条件时被唤醒。通常与互斥锁一起使用,线程在等待条件满足时会释放互斥锁并进入睡眠状态,当条件满足时,其他线程可以通过唤醒操作通知等待的线程。在 C 语言的 POSIX 线程库中,使用 pthread_cond_t 类型定义条件变量,pthread_cond_wait 函数用于等待条件变量,pthread_cond_signal 或 pthread_cond_broadcast 函数用于唤醒等待的线程。

互斥量(互斥锁)

互斥锁的使用步骤

创建互斥锁: pthread_mutex_t mutex;

初始化:pthread_mutex_init(&mutex,NULL); //mutex = 1

找到线程共同操作的共享数据
加锁: 操作共享资源之前加锁,pthread_mutex_lock(&mutex); //阻塞 :mutex = 0

pthread_mutex_trylock(&mutex); // 如果锁上锁直接返回,不阻塞

XXXXXX共享数据操作 //临界区 ,越小越好

解锁: pthread_mutex_unlock(&mutex); // mutex = 1

阻塞在锁上的线程会被唤醒

**销毁:**pthread_mutex_destory(&mutex);

相关函数

初始化互斥锁

复制代码
#include <pthread.h>

int pthread_mutex_init(
pthread_mutex_t *restrict mutex, 
const pthread_mutexattr_t *restrict attr);

/*参数
pthread_mutex_t *restrict mutex:指向要初始化的互斥锁对象的指针。
const pthread_mutexattr_t *restrict attr:指向互斥锁属性对象的指针,传入 NULL 表示使用默认属性。
返回值
如果函数调用成功,返回 0。
如果调用失败,返回一个非零的错误码*/

加锁(获取互斥锁)

没有被锁上,当前线程会将这把锁锁上

被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞

复制代码
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

/*参数
pthread_mutex_t *mutex:指向互斥锁的指针
返回值
成功:如果函数调用成功获取到互斥锁,返回值为 0。
失败:若调用过程中出现错误,会返回一个非零的错误码*/

尝试加锁

没有锁上:当前线程会被这把锁加锁

如果锁上了:不会阻塞,返回

复制代码
#include <pthread.h>

pthread_mutex_trylock(pthread_mutex_t* mutex);

/*参数
pthread_mutex_t *mutex:指向互斥锁的指针
返回值
成功:如果函数调用成功获取到互斥锁,返回值为 0。
失败:若调用过程中出现错误,会返回一个非零的错误码*/

解锁(释放互斥锁)

复制代码
#include <pthread.h>

pthread_mutex_unlock(pthread_mutex_t* mutex);

/*参数
pthread_mutex_t *mutex:指向互斥锁的指针
返回值
成功:如果函数调用成功获取到互斥锁,返回值为 0。
失败:若调用过程中出现错误,会返回一个非零的错误码*/

销毁互斥锁

复制代码
#include <pthread.h>

pthread_mutex_destory(pthread_mutex_t* mutex );

/*参数
pthread_mutex_t *mutex:指向互斥锁的指针
返回值
成功:如果函数调用成功获取到互斥锁,返回值为 0。
失败:若调用过程中出现错误,会返回一个非零的错误码*/

死锁

1.自己锁自己

复制代码
for(int i = 0;i<MAX;i++)
{
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);//造成死锁
int crt = number;
crt++;
number = crt;
printf("thread A id = %ld,number = %d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
usleep(10);
}

2.两个或多个线程(进程)因互相等待对方释放资源而陷入无限等待的状态


造成原因:
线程 1 对共享资源 A 加锁成功 -A 锁
线程 2 对共享资源 B 加锁成功 -B 锁
线程 1 访问共享资源 B ,对 B 锁加锁 - 线程 1 阻塞在 B 锁上
线程 2 访问共享资源 A ,对 A 锁加锁 - 线程 2 阻塞在 A 锁上
解决方法:
1.让线程按照一定的顺序去访问共享资源
2.在访问其他锁的时候,需要先将自己的锁解开
3.try_lock

读写锁

读写锁允许多个线程同时对共享资源进行读访问,因为读操作通常不会修改共享资源,所以多个读操作可以并发执行,不会产生数据竞争问题。但在进行写操作时,读写锁会保证在同一时刻只有一个线程能够对共享资源进行写访问,并且在写操作进行时,不允许其他线程进行读或写操作,以确保数据的一致性。

读共享 :多个线程可以同时持有读锁,并行地读取共享资源,提高了读操作的并发性能。
写独占 :在进行写操作时,写锁会独占资源,不允许其他线程进行读或写操作,保证了写操作的原子性和数据的一致性。
读写互斥:当有线程持有写锁时,其他线程不能获取读锁或写锁;当有线程持有读锁时,其他线程不能获取写锁,但可以获取读锁。

相关函数

复制代码
pthread_rwlock_t lock;//创建读写锁

初始化读写锁

复制代码
#include <pthread.h>

int pthread_rwlock_init(
pthread_rwlock_t *restrict rwlock, 
const pthread_rwlockattr_t *restrict attr);

/*参数:
rwlock:指向要初始化的读写锁对象的指针。
attr:指向读写锁属性对象的指针,若为 NULL,则使用默认属性。
返回值:成功返回 0,失败返回错误码。*/

获取读锁

复制代码
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

/*参数:rwlock 指向要获取读锁的读写锁对象的指针。
  返回值:成功返回 0,失败返回错误码*/

获取写锁

复制代码
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

/*参数:rwlock 指向要获取读锁的读写锁对象的指针。
  返回值:成功返回 0,失败返回错误码*/

释放读锁或写锁

复制代码
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

/*参数:rwlock 指向要获取读锁的读写锁对象的指针。
  返回值:成功返回 0,失败返回错误码*/

销毁读写锁

复制代码
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

/*参数:rwlock 指向要获取读锁的读写锁对象的指针。
  返回值:成功返回 0,失败返回错误码*/

条件变量

条件变量是线程间进行通信和同步的工具,它允许一个或多个线程等待某个条件变为真。当条件不满足时,线程可以进入等待状态;当其他线程改变了共享资源的状态并使条件满足时,这些线程可以通过条件变量唤醒等待的线程。
**条件变量通常与互斥锁一起使用。**互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问该资源;条件变量则用于线程之间的通信,当某个线程发现条件不满足时,它会释放互斥锁并进入等待状态,直到其他线程发出信号表示条件已满足。当收到信号后,等待的线程会重新获取互斥锁并继续执行。

相关函数

复制代码
pthread_cond_t cond ;//创建条件变量

初始化条件变量

复制代码
#include <pthread.h>

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

/*参数:
cond:指向要初始化的条件变量对象的指针。
attr:指向条件变量属性对象的指针,若为 NULL,则使用默认属性。
返回值:成功返回 0,失败返回错误码。*/

等待条件变量

复制代码
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

/*参数:
cond:指向要等待的条件变量对象的指针。
mutex:指向与条件变量配合使用的互斥锁对象的指针。
返回值:成功返回 0,失败返回错误码。
该函数会原子性地释放互斥锁并使线程进入等待状态,直到收到条件变量的信号,然后重新获取互斥锁。*/

限时等待条件变量

复制代码
#include <pthread.h>
#include <time.h>

int pthread_cond_timedwait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);

/*参数:
第三个参数指向一个 struct timespec 类型的结构体指针,用于指定等待的绝对时间

返回值
成功:如果线程在超时时间内被信号唤醒,函数返回 0。
超时:如果在指定的超时时间内没有收到信号,函数返回 ETIMEDOUT。
错误:如果出现其他错误,函数返回相应的错误码*/

唤醒一个等待线程

复制代码
int pthread_cond_signal(pthread_cond_t *cond);

/*参数:cond 指向要发送信号的条件变量对象的指针。
  返回值:成功返回 0,失败返回错误码。
  该函数会唤醒一个等待在该条件变量上的线程。*/

唤醒所有等待线程

复制代码
int pthread_cond_broadcast(pthread_cond_t *cond);

/*参数:cond 指向要发送信号的条件变量对象的指针。
  返回值:成功返回 0,失败返回错误码。
  该函数会唤醒所有等待在该条件变量上的线程。*/

销毁条件变量

复制代码
int pthread_cond_destroy(pthread_cond_t *cond);

/*参数:cond 指向要销毁的条件变量对象的指针。
返回值:成功返回 0,失败返回错误码*/

举例:

使用条件变量实现生产者,消费者模型

复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct node
{
        int data;
        struct node *next;


}Node;

//create head node
Node *head = NULL;

//create mutex
pthread_mutex_t mutex;

//create cond
pthread_cond_t cond;

void *produce(void *arg)
{
        while(1)
        {

                //create node
                Node *pnew = (Node*)malloc(sizeof(Node));
                //init node
                pnew->data = rand()%1000;

                //lock 
                pthread_mutex_lock(&mutex);

                pnew->next = head;
                head = pnew;
                printf("produce: %ld,%d\n",pthread_self(),pnew->data);

                //unlock
                pthread_mutex_unlock(&mutex);
                pthread_cond_signal(&cond);

                sleep(rand()%3);
        }

        return NULL;
}

void* customer(void *arg)
{
        while(1)
        {
                //lock
                pthread_mutex_lock(&mutex);
                if(head == NULL)
                {
                        pthread_cond_wait(&cond,&mutex);
                }
                //delete head node
                Node* pdel = head;
                head = head->next;
                printf("customer: %ld,%d\n",pthread_self(),pdel->data);
                free(pdel);
                //unlock
                pthread_mutex_unlock(&mutex);
        }
        return NULL;
}
int main()
{
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);
        pthread_t p1,p2;

        pthread_create(&p1,NULL,produce,NULL);
        pthread_create(&p2,NULL,customer,NULL);

        pthread_join(p1,NULL);
        pthread_join(p2,NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

 }

信号量

复制代码
sem_t sem;//创建信号量

相关函数

初始化信号量

复制代码
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

/*参数:
sem:指向要初始化的信号量对象的指针
pshared:0-线程同步
         1-进程同步
value:信号量的初始值,它代表了可用资源的初始数量。
返回值:若初始化成功,返回 0;若失败,返回 -1*/

等待信号量

执行 P 操作,即尝试获取信号量。如果信号量的值大于 0,则将信号量的值减 1 并继续执行;如果信号量的值为 0,则调用线程会被阻塞,直到信号量的值大于 0。

复制代码
#include <semaphore.h>

int sem_wait(sem_t *sem);

/*
参数:sem 指向要操作的信号量对象的指针。
返回值:若操作成功,返回 0;若失败,返回 -1*/

非阻塞等待信号量

复制代码
#include <semaphore.h>

int sem_trywait(sem_t *sem);

/*
参数:sem 指向要操作的信号量对象的指针。
返回值:若成功获取信号量,返回 0;若信号量不可用,返回 -1 */

限时等待信号量

复制代码
#include <semaphore.h>
#include <time.h>

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

/*参数:
sem:指向要操作的信号量对象的指针。
abs_timeout:指向 struct timespec 结构体的指针
返回值:若成功获取信号量,返回 0;若超时,返回 -1 */

释放信号量

复制代码
#include <semaphore.h>

int sem_post(sem_t *sem);

/*
参数:sem 指向要操作的信号量对象的指针。
返回值:若操作成功,返回 0;若失败,返回 -1*/

销毁信号量

复制代码
#include <semaphore.h>

int sem_destroy(sem_t *sem);

/*
参数:sem 指向要操作的信号量对象的指针。
返回值:若操作成功,返回 0;若失败,返回 -1*/
相关推荐
夜听莺儿鸣10 小时前
502-003_Linux 中断与异常(一):从Linux角度理解中断与异常
linux·中断与异常·linux中断
杨云龙UP10 小时前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
向成科技11 小时前
XC3576H工控主板|深度适配Ubuntu 26.04 LTS,释放边缘AI与工业开发新潜能
linux·人工智能·ubuntu·机器人·硬件·主板·边缘ai
Fcy64812 小时前
Linux下 进程间关系与守护进程
linux·运维·服务器·守护进程
码农小韩12 小时前
Linux驱动理论(二)——Linux字符设备驱动
linux·嵌入式软件开发·linux操作系统·linux应用开发·linux驱动理论
well061212 小时前
Linux粘滞位与Makefile机制深度解析
linux·运维·服务器
再写一行代码就下班12 小时前
linux sh脚本在windows修改导致无法使用解决方式
java·linux·centos
额额额对了13 小时前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
子木HAPPY阳VIP13 小时前
Ubuntu 关闭防火墙操作步骤
linux·运维·ubuntu
王振超wzc13 小时前
嵌入式开发环境搭建--WM软件安装,Ubuntu操作系统安装
linux·运维·ubuntu