关于线程的那些事

关于线程的那些事

概念

与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的 UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)

进程是 CPU 分配资源的最小单位,线程是操作系统调度执行的最小单位。 (线程资源共享)

线程是轻量级的进程(LWP: Light Weight Process),在 Linux 环境下线程的本质仍是进程。

Linux中如何查看指定的进程号: ps -Lf pid

线程与进程的区别

进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。

调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。

线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。

创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。 (只是代码区与栈区产生了分割)

进程的共享资源与非共享资源

在这里插入图片描述

Linux中NPTL的线程操作

在这里插入图片描述

线程属性

在这里插入图片描述

线程同步

线程的优势是能够通过全局变量来共享信息。不过,这种便捷的共享是有代价的:必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。

而解决这个问题需要关注的区域就是“临界区”,它指的是访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应终端该片段的执行。

由此引出了线程同步的定义:

当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。

解决线程同步的几个方法:

互斥锁

读写锁

条件变量

信号量

互斥锁

为避免线程更新共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。
一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:
针对共享资源锁定互斥量->访问共享资源->对互斥量解锁 (流程图如下)

在这里插入图片描述

例子:

/*
    互斥量的类型 pthread_mutex_t
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
        - 初始化互斥量
        - 参数 :
            - mutex : 需要初始化的互斥量变量
            - attr : 互斥量相关的属性,NULL
        - restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
            pthread_mutex_t *restrict mutex = xxx;
            pthread_mutex_t * mutex1 = mutex;

int pthread_mutex_destroy(pthread_mutex_t *mutex);

   - 释放互斥量的资源

int pthread_mutex_lock(pthread_mutex_t *mutex);

   - 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待

int pthread_mutex_trylock(pthread_mutex_t *mutex);

   - 尝试加锁,如果加锁失败,不会阻塞,会直接返回。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

   - 解锁

*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;

// 创建一个互斥量
pthread_mutex_t mutex;



void * sellticket(void * arg) {
// 加锁
//pthread_mutex_lock(&mutex);可能出现的错误,在此处加锁会导致没有多线程操作。
// 卖票
while(1) {

​    // 加锁
​    pthread_mutex_lock(&mutex);

​    if(tickets > 0) {
​        usleep(6000);
​        printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
​        tickets--;
​    }else {
​        // 解锁
​        pthread_mutex_unlock(&mutex);
​        break;
​    }

​    // 解锁
​    pthread_mutex_unlock(&mutex);
}


​    

return NULL;

}

int main() {

// 初始化互斥量
pthread_mutex_init(&mutex, NULL);

// 创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sellticket, NULL);
pthread_create(&tid2, NULL, sellticket, NULL);
pthread_create(&tid3, NULL, sellticket, NULL);

// 回收子线程的资源,阻塞
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);

pthread_exit(NULL); // 退出主线程

// 释放互斥量资源
pthread_mutex_destroy(&mutex);

return 0;}
死锁

死锁发生的场景:

忘记释放锁
重复加锁(出现在多个函数中重复调用加锁出现的比较多)
多线程多锁,抢占锁资源 (如下图)
在这里插入图片描述

读写锁

读写锁是为了解决多线程只是想读取共享数据而不需要写入共享数据的时候可以正常运行。读写锁可以允许多个线程对临界区进行写操作但是允许多个线程进行读操作。

其具体特性如下:

如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
如果有其它线程写数据,则其它线程都不允许读、写操作。
写是独占的,写的优先级高。

生产者消费者模型

在这里插入图片描述

为了解决队列空时消费者不继续消费判断,或者生产者不生产溢出而引入的性质。

其核心是以下函数

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, const struct timespec *restrict abstime);  
-等待多长时间,使用其会让线程阻塞直到指定时间结束
int pthread_cond_signal(pthread_cond_t *cond);  
-唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);  
-唤醒所有的等待线程

信号量

类似信号灯,不但是线程中通讯的一种方式,也是进程中通讯的一种方式,其运行流程如下所示:

1.系统首先要检测该资源的信号量;

2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;

3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;

当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值