linxu多线程(进程与线程区别,互斥同步)(代码片段)

西科陈冠希 西科陈冠希     2022-12-15     624

关键词:

互斥同步

线程

线程概念

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

线程优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的 I/O操作

线程缺点

  • 性能损失
    • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低
    • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制
    • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高
    • 编写与调试一个多线程程序比单线程程序困难得多

线程异常

  1. 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  2. 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

Linux进程VS线程

概念

进程是资源分配的基本单位

线程是调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  1. 线程ID
  2. 一组寄存器
  3. errno
  4. 信号屏蔽字
  5. 调度优先级

关系

线程控制

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

创建线程

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
  • 错误检查:
    • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
    • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通
      过返回值返回
    • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

线程ID及进程地址空间布局

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
  • pthread_t pthread_self(void);

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

线程终止

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数:

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

pthread_cancel函数:

功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

线程等待

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。

功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

int pthread_detach(pthread_t thread);

互斥

背景知识

临界资源:多线程执行流共享的资源就叫做临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

互斥量mutex

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

互斥条件

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。

如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。

如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

互斥量的接口

过程:
分配锁:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

初始化锁:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
参数:
mutex:要初始化的互斥量
attr:NULL

互斥量加锁和解锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号

销毁锁:

int pthread_mutex_destroy(pthread_mutex_t *mutex)

实例运用(售票系统)

int n=1000;
pthread_mutex_t lock;
void* ticket(void* arg)

  char* id=(char*) arg;
  sleep(1);
  while(1)
  
    usleep(1000);
    pthread_mutex_lock(&lock);
    if(n>0)
    
    usleep(1000);
    printf("thread_id:%s,ticket:%d\\n",id,n);
      n--;
      pthread_mutex_unlock(&lock);
    
    else
      pthread_mutex_unlock(&lock);
      break;
    

  


int main()

  pthread_t t1;
  pthread_t t2;
  pthread_t t3;
  pthread_t t4;
  pthread_mutex_init(&lock,NULL);
  pthread_create(&t1,NULL,ticket,"thead 1");
  pthread_create(&t2,NULL,ticket,"thead 2");
  pthread_create(&t3,NULL,ticket,"thead 3");
  pthread_create(&t4,NULL,ticket,"thead 4");
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);
  pthread_join(t3,NULL);
  pthread_join(t4,NULL);
  pthread_mutex_destroy(&lock);


互斥量实现原理探究

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下

整个过程中为1的mutex只有一份,所以加锁是原子的

死锁

概念

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

破坏死锁的四个必要条件
加锁顺序一致
避免锁未释放的场景
资源一次性分配

同步

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

条件变量过程描述

初始化:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL

销毁:

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足:

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待:

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

pthread_cond_wait 需要互斥量

条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。

条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。

int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样

pthread_mutex_t lock;
pthread_cond_t cond;
void *r1(void* arg)

  char* name=(char*) arg;
  while(1)
  
    pthread_cond_wait(&cond,&lock);
    printf("liveing\\n");
  

void* r2(void* arg)

  char *name =(char*)arg;
  while(1)
  
    sleep(2);
    pthread_cond_signal(&cond);
  

int main()

  pthread_t t1;
  pthread_t t2;
  pthread_mutex_init(&lock,NULL);
  pthread_create(&t1,NULL,r1,"thead 1");
  pthread_create(&t2,NULL,r2,"thead 2");
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);
  pthread_mutex_destroy(&lock);
  pthread_cond_destroy(&cond);



并发互斥并行同步异步多线程的区别

并发互斥并行同步异步多线程的区别并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥互斥:进程间相互排斥... 查看详情

多线程的同步和互斥有啥区别

参考技术A线程同步是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率低线程异步:访问资源时在空闲等待时同时访问其他资源,实现多线程机制异步处理就是,你现在问我问题,我可以不回答你,等我用时间了再... 查看详情

linux多线程(代码片段)

线程一、Linux线程概念1、什么是线程2、原生线程库pthread3、线程的优点4、线程的缺点5、线程异常6、线程的用途二、Linux进程VS线程1、进程和线程2、进程的多个线程共享3、进程和线程的区别三、POSIX线程库1、线程创建2、线程等... 查看详情

linux多线程(代码片段)

线程一、Linux线程概念1、什么是线程2、原生线程库pthread3、线程的优点4、线程的缺点5、线程异常6、线程的用途二、Linux进程VS线程1、进程和线程2、进程的多个线程共享3、进程和线程的区别三、POSIX线程库1、线程创建2、线程等... 查看详情

linux多线程(代码片段)

多线程一、线程是什么二、线程的优缺点线程的优点线程的缺点线程异常线程用途进程和线程对比三、线程的控制POSIX线程库线程ID和进程ID线程ID及进程地址空间布局线程终止线程等待线程分离三、线程互斥进程线程间的互斥互... 查看详情

线程与进程

线程,线程的定义,线程的四种状态;多线程,多线程的同步和互斥的几种实现方法,用户模式(原子操作、临界区),内核模式(事件、信号量、互斥量)。linux下线程同步的三种方法,事件、信号量、互斥量。进程,进程的... 查看详情

多线程并发编程(代码片段)

文章目录多线程并发编程一、多线程带来的问题相关概念二、互斥1、互斥与互斥量2、申请互斥量I.静态方法申请互斥量:II.动态方法申请互斥量:3、利用互斥量加锁与解锁4、销毁互斥量5、互斥量综合应用——模拟抢票6... 查看详情

高并发基石多线程守护线程线程安全线程同步互斥锁(代码片段)

学习目录前言一、进程与线程二、线程的创建1.继承Thread类2.实现Runable接口3.匿名内部类实现4.实现Callable、FutureTask接口三、线程创建的本质(🚩)四、Thread常用API、构造器五、用户线程与守护线程六、线程安全七、... 查看详情

并发并行同步异步多线程的区别

1.并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥2.互斥:进程间相互排斥的使用临界资源的现象,就叫互... 查看详情

27apr18gil多进程多线程使用场景线程互斥锁与gil对比基于多线程实现并发的套接字通信进程池与线程池同步异步阻塞非阻塞(代码片段)

27Apr18一、全局解释器锁(GIL)运行test.py的流程:a、将python解释器的代码从硬盘读入内存b、将test.py的代码从硬盘读入内存 (一个进程内装有两份代码)c、将test.py中的代码像字符串一样读入python解释器中解析执行1、GIL:全局... 查看详情

秒杀多线程第七篇经典线程同步互斥量mutex(代码片段)

阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇一个经典的多线程同步问题》《秒杀多线程第五篇经典线程同步关键段CS》《秒杀多线程第六篇经典线程同步事件Event》 前面介绍了关键段CS、事件Event在经典线程同步... 查看详情

多线程与多进程的区别

(1)多线程多进程的区别维度多进程多线程总结数据共享、同步数据是分开的:共享复杂,需要用IPC;同步简单多线程共享进程数据:共享简单;同步复杂各有优势内存、CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简... 查看详情

linux线程的创建与同步(代码片段)

Linux线程线程的概念与实现方式线程与进程的区别线程的实现方式线程的使用线程库中的接口等待一个线程结束线程同步多线程并发访问同一块内存的问题使用互斥锁实现线程同步线程安全多线程中执行fork()线程的概念与实现方... 查看详情

进程互斥与同步

1、解释并发与并行,并说明两者关系。并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间... 查看详情

linux系统编程多线程(代码片段)

多线程初识线程线程的概念线程的优缺点优点缺点了解pid(轻量级线程号)与tgid(线程组id)进程与线程概念解释同一所属组下多线程之间共享与独立的空间进程与线程的对比多进程与多线程对比线程控制线程的创建线程终止线程等... 查看详情

linux系统编程多线程(代码片段)

多线程初识线程线程的概念线程的优缺点优点缺点了解pid(轻量级线程号)与tgid(线程组id)进程与线程概念解释同一所属组下多线程之间共享与独立的空间进程与线程的对比多进程与多线程对比线程控制线程的创建线程终止线程等... 查看详情

进程与线程对比/进程与线程的区别

...能够完成多任务,比如在一台电脑上能够同时运行多个QQ线程,能够完成多任务,比如一个QQ中的多个聊天窗口2.定义对比进程是系统进行资源分配基本单位,每启动一个进程操作系统都需要为其分配运行资源。线程是运行程序中... 查看详情

linuxbingc(多线程)(代码片段)

上一篇目录标题线程概念曾经写的代码当中有没有线程呢线程的共享与独有线程的优缺点:线程控制线程创建结论线程终止获取线程标识符线程等待线程分离线程不安全状态同步与互斥互斥互斥锁信号量的计数器当中如何保证原... 查看详情