线程

本文深入讲解了线程的概念,包括线程与进程的区别、线程的实现方式、创建及结束线程的方法,并探讨了线程间如何进行通信与同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程的概念:

    线程是进程内部的一条执行序列(执行流),一个进程可以包含多条线程(main 函数所代表的执行序列,主线程)。通过函数库创建线程—函数线程。一个进程中所有线程是并发执行的。同一个进程的所有线程都有独立的栈区
    主线程代表进程执行的第一条线程而已,当主线程通过线程库创建出函数线程以后,两个线程就没有任何区别

进程与线程的区别:

  1. 线程是执行的最小单位,是CPU调度的最小单位,进程是资源分配的最小单位
  2. 一个进程可以包含多条线程。进程是独立的执行个体,线程是进程内部的执行序列
  3. 进程切换效率低,代价大。线程切换效率高,代价小
  4. 线程中的局部变量不共享,全局变量,堆区变量,文件描述符共享
    进程文件描述符共享,全局变量,堆区变量,局部变量不共享
  5. 进程是安全的,线程是不安全的
  6. 线程是轻量级的进程。线程通过改变 eax、ebx 等寄存器的值进行切换

线程的实现方式:

1、用户级线程
     1)内核简单,但是用户程序实现复杂,线程的创建、调度、管理都需要用户程序自己完成
     2)如果进程中的一条线程阻塞,则这个进程被阻塞
     3)切换效率高,不需要陷入内核
在这里插入图片描述
2、内核级线程
     线程的创建、调度、管理都由操作系统内核支持。
     效率低,切换时与用户级线程比较,切换效率低,每次切换都要从用户态切换到内核态
在这里插入图片描述
3、混合级线程
     多个线程用户共享一个内核线程
在这里插入图片描述

线程的使用

libpthread.so
线程的创建函数:

int pthread_creat (pthread_t *id, pthread_attr_t *attr, (void*)(*pthread_fun(void*)), void*arg);

    id:用于获取系统创建的线程的 ID 值,传递一个变量的地址
    attr:线程的属性,默认属性 NULL
    pthread_fun 线程函数,指定新建线程的执行序列
    arg:传递给线程函数的参数

pthread_creat (); //创建线程是给线程函数传参的方式:
     1、值传递 值最大4个字节。
          将传递的值直接强转给 void*
          arg:类型是void* 记录的传递的值
     2、地址传递 实现了线程间的栈区数据共享
          将要传递的值的地址转化为 void*
          arg:类型是void* 记录的传递的地址
       主线程后期对值的修改可能影响函数线程中获取值,函数线程中通过地址对变量修改,也会主线程中变量的值

主线程结束时,默认调用 exit 函数,结束的是进程

测试结论:
     1、pthread_creat 创建一个新的线程,新线程执行的指令序列是 pthread_fun 指针指向的函数
     2、进程中的线程是并发执行的,线程的执行顺序是不一定的,由系统决定
     3、线程是轻量级进程

     操作系统为进程提供一个 4G 的虚拟空间,进程中所有的线程都共享虚拟空间,页表也是共享的。
     线程的运行在栈上开辟,在主函数运行开辟的栈的上面。这两份空间是同时执行的
     创建线程只会在进程中申请栈区资源,用于线程函数的执行,进程中的数据区域、堆区多线程都是使用同一份,都是共享的

一个进程中的所有线程
     全局变量 .data 共享
     局部变量 .stack 不共享
     堆区变量 .heap 共享
     文件描述符 fd 共享

在这里插入图片描述

线程结束的函数:int pthread_exit (void *reval);
reval:设置线程的退出状态
等待线程结束函数/获取线程退出状态:
     int pthread_join (pthread_t id, void **rval);会阻塞运行
     waitpid (pid_t pid, … );
     主动结束一个线程:int pthread_cancel (pthread_t id);

线程间通讯
1、将数据定义到全局
2、将数据保存到堆区
3、创建线程时,将栈区数据的地址传递给函数线程
线程同步
线程间协同工作,用于多线程访问临界资源时,必须按照一定的先后顺序访问执行
详情

     信号量:类似于计数器,记录临界资源的数量

	#include <semaphore.h>
	sem_t sem;
	int sem_init(sem_t *sem, int shared, int val);
		shared:信号量是否可以在进程间共享,Linux不支持
		val:设置的信号 量的初始值
	int sem_wait(sem_t *sem); // P操作wait等待
	int sem_post(sem_t *sem); // V操作
	int sem_destroy(sem_t *sem); //销毁信号堂

     互斥锁(读写锁、互斥锁):一个锁有两种状态 加锁 解锁

	#include <pthread.h>  
	pthread mutex_t mutex //全局变量

	int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
	int pthread_mutex_lock(ptread_mutex_t *mutex); //加锁.会阻塞
	int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁,不会阻塞
	int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
	int pthread_mutex_destroy(pthread_mutex_t *mutex); //销毁锁

线程安全
     多线程环境下,操作共享的资源时,可能会发生一些问题 —》 执行的结果不确定
安全原因:

  1. 线程是并发执行的或者是并行的
  2. 操作是非原子操作,在并发系统上
  3. 操作是非互斥的,在并行系统上
  4. 操作对象是同一个 线程之间数据共享

可重入函数 strtok_r
     char *strtok (char *buff, char *flag);
     char *strtok_r (char *buff, char *flag, char **rtptr);

                                        POSIX 不能保证线程安全的函数POSIX不能保证线程安全的函数
多线程环境下调用fork创建的子进程
     1、如果多线程环境下,一个线程调用fork创建子进程,子进程仅仅会将调用fork的那个线程启动,其他线程并不会启动
     2、如果多进程环境下对锁进行加锁,然后创建进程,则子进程中对锁的加锁可能出现死锁–》子进程会复制父进程的锁
     3、保证子进程复制父进程的锁是解锁状态

注册函数

int pthread_atfork(void(*parpare)(void),void(*parent)(void),void(*child)(void));

     void(*parpare)(void):fork 调用初始,先调用 parpare 函数,其作用是给所有的锁加锁如果有锁处于加锁状态,则 fork 函数会被阻塞,等待解锁。
     void(*parent)(void):parent 和 child 函数在 fork 执行完成之后,分别在父进程空间和子进程空间调用,其作用是对所有的锁解锁
     void(*child)(void):fork 之前调用

void pthread_fun(void *arg)
{
	pthread_mutex_lock(&mutex);
	sleep(3):
	unlock();
}
int main()
{
	.....
	sleep(1);保证函数线程能够加锁
	pit_t pid=fork();//fork执行时,mutex是函数线程加锁状态
	if(pid==0)
	{
		lock(&mutex);
		printf();
	}
	else
	{
		lock(&mutex);
		printf();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值