Linux探秘坊-------15.线程概念与控制

1.线程概念

1.什么是线程



2.线程 vs 进程

不同的操作系统有不同的实现方式:

  • linux :直接使用pcb的功能来模拟线程,不创建新的数据结构
  • windows: 使用新的数据结构TCB ,来进行实现,一个PCB里有很多个TCB

3.资源划分

详情可见操作系统书籍中的存储器管理虚拟存储器管理章节!!!!

4.线程理解



2.线程和进程的区别

  • 黄字代表线程特有的私有数据
  • 一组寄存器和上下文数据---------------证明线程是可以被独立调用
  • 栈--------------证明线程是动态的

1.进程的线程共享!!!!!!!!!!

同⼀地址空间 ,因此 Text Segment 、 Data Segment 都是共享的,如果定义⼀个函数,在各线程中都可以调⽤ ,如果定义⼀个全局变量,在各线程中都可以访问到, 除此之外,各线程还共享以下进程资源和环境:

  • ⽂件描述符表 (fd)
  • 每种信号的处理⽅式(SIG_IGN、SIG_DFL或者⾃定义的信号处理函数)
  • 当前⼯作⽬录
  • ⽤⼾id和组id

1.父子进程只有代码段是共享的,但主线程和子线程连地址空间都是共享的,所以他们可以使用共享的函数和全局变量 意思就是如果子线程的全局变量被修改了,主线程看到的是同一个全局变量,也会变化 轻松实现类似进程间通信!!!!!!!

2.而全局变量在父子进程中是写实拷贝,子变父不变 !!!!!!!!!

3.linux的线程控制

1.线程创建

创建函数:

运行后使用 ps - aL 指令查看线程

2.pthread库的引入----为什么需要有线程库?

4.pthread库的使用

  • 与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是"pthread_"打头的
    • 要使⽤这些函数库,要通过引⼊头⽂件 <pthread.h>
    链接这些线程函数库时要使⽤编译器命令的"-lpthread"选项

1.线程创建---pthread_create()

  • thread是新线程的标识符,是输出型参数(让主线程获取,便于调用其他函数)

2.线程等待---pthread_join()

  • 这里retval拿到的是子线程的退出码,即子线程函数的返回值,但返回值是void *
  • 所以retval的类型应当是void * 的地址类型即void**
  • 其中,routine是子线程的入口函数,routine函数结束的话子线程也就结束了

3.线程取消或终止---pthread_exit()/pthread_cancel()

1-----------------pthread_exit()

2-----------------pthread_cancel()

  • 如果线程被主线程或其他线程取消,那么主线程join函数得到的返回值固定为-1

4.线程分离---int pthread_detach(pthread_t thread);

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

1.--------------------------pthread_self函数获取id

2.--------------------------pthread库的动态链接

  • 所有的线程都是在thread库中建立的,线程的管理块都存储在库中具体的pcb由用户使用系统调用在内核中建立
  • 创建线程的具体图例

6.线程互斥

来看一个买票的例子:

cpp 复制代码
/ 操作共享变量会有问题的售票系统代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
void* route(void* arg) {
    char* id = (char*)arg;
    while (1) {
        if (ticket > 0) {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        } else {
            break;
        }
    }
}
int main(void) {
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, (void*)"thread 1");
    pthread_create(&t2, NULL, route, (void*)"thread 2");
    pthread_create(&t3, NULL, route, (void*)"thread 3");
    pthread_create(&t4, NULL, route, (void*)"thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}
  • 为了避免买票变成负数,所以要使用 来保证线程间的互斥

为什么会变成负数??????

  • 因为ticket--操作并不是原子的----------即无法一步完成------要先把ticket大小传入cpu,再在cpu中进行运算,最后再写回ticket全局变量中--------一共有三步
  • 可能某一个线程计算完后ticket为0,但还没写回ticket,另一个线程就又开始运行,这个时候ticket是1,还能通过if条件语句,最后两个线程都执行ticket--,全部写回后,ticket变成了-1!!!!

1.锁的使用

cpp 复制代码
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
pthread_mutex_t mutex;//设置锁----这里是全局锁,用完后会自动销毁
void* route(void* arg) 
{
    char* id = (char*)arg;
    while (1) 
    {
        pthread_mutex_lock(&mutex);//pthread_mutex_lock--------上锁
        if (ticket > 0) {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解锁
        } else {
            pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解锁
            break;
        }
    }
    return nullptr;
}
int main(void) {
    pthread_t t1, t2, t3, t4;
    pthread_mutex_init(&mutex, NULL);// pthread_mutex_init------初始化锁
    pthread_create(&t1, NULL, route, (void*)"thread 1");
    pthread_create(&t2, NULL, route, (void*)"thread 2");
    pthread_create(&t3, NULL, route, (void*)"thread 3");
    pthread_create(&t4, NULL, route, (void*)"thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    pthread_mutex_destroy(&mutex);//pthread_mutex_destroy-------删除锁
}

pthread 库是 POSIX 线程库,提供了多线程编程的 API,其中包括用于线程同步的锁机制。以下是 pthread 中常见的锁函数及其解释:


1. 互斥锁(Mutex)

互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

相关函数:
  • pthread_mutex_init

    初始化互斥锁。

    c 复制代码
    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    • mutex:指向互斥锁对象的指针。
    • attr:属性对象(通常设为 NULL 使用默认属性)。
    • 成功返回 0,失败返回错误码。
  • pthread_mutex_lock

    加锁。如果锁已被其他线程持有,则调用线程阻塞直到锁被释放。

    c 复制代码
    int pthread_mutex_lock(pthread_mutex_t *mutex);
  • pthread_mutex_trylock

    尝试加锁,如果锁已被持有则立即返回错误(非阻塞)。

    c 复制代码
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    • 成功返回 0,锁被持有时返回 EBUSY
  • pthread_mutex_unlock

    解锁。

    c 复制代码
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • pthread_mutex_destroy

    销毁互斥锁,释放资源。

    c 复制代码
    int pthread_mutex_destroy(pthread_mutex_t *mutex);

7.线程同步

1.条件变量函数介绍

-------------------为了避免加锁后导致线程饥饿而设置的变量

条件变量用于线程间通信,通常与互斥锁配合使用,实现线程的等待和唤醒机制。

相关函数:
  • pthread_cond_init //第二个变量基本是设置为nullptr

    初始化条件变量。

    c 复制代码
    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • pthread_cond_wait // 第二个参数是上文的互斥锁

    等待条件变量,并释放关联的互斥锁(原子操作)。

    c 复制代码
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • pthread_cond_signal

    唤醒一个等待该条件变量的线程。

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

    唤醒所有等待该条件变量的线程。

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

    销毁条件变量。

    c 复制代码
    int pthread_cond_destroy(pthread_cond_t *cond);
cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>

#define NUM 5
int cnt = 1000;

pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 定义锁
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;   // 定义条件变量

// 等待是需要等,什么条件才会等呢?票数为0,等待之前,就要对资源的数量进行判定。
// 判定本身就是访问临界资源!,判断一定是在临界区内部的.
// 判定结果,也一定在临界资源内部。所以,条件不满足要休眠,一定是在临界区内休眠的!
// 证明一件事情:条件变量,可以允许线程等待
// 可以允许一个线程唤醒在cond等待的其他线程, 实现同步过程
void *threadrun(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&glock);
        // 直接让对用的线程进行等待?? 临界资源不满足导致我们等待的!
        pthread_cond_wait(&gcond, &glock); // glock在pthread_cond_wait之前,会被自动释放掉
        std::cout << name << " 计算: " << cnt << std::endl;
        cnt++;
        pthread_mutex_unlock(&glock);
    }
}

int main()
{
    std::vector<pthread_t> threads;
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char *name = new char[64];
        snprintf(name, 64, "thread-%d", i);//snprintf函数往name中打印字符串
        int n = pthread_create(&tid, nullptr, threadrun, name);
        if (n != 0)
            continue;
        threads.push_back(tid);
        sleep(1);
    }

    sleep(3);

    // 每隔1s唤醒一个线程
    while(true)
    {
        std::cout << "唤醒所有线程... " << std::endl;
        pthread_cond_broadcast(&gcond);
        
        // std::cout << "唤醒一个线程... " << std::endl;
        // pthread_cond_signal(&gcond);
        sleep(1);
    }

    for (auto &id : threads)
    {
        int m = pthread_join(id, nullptr);
        (void)m;
    }

    return 0;
}


8.生产者消费者模型


  • 生产者和消费者之间要有顺序地进行工作,所以是同步的
基于阻塞队列实现生产者消费者模型:

----------------什么是阻塞队列?

相关推荐
NotStrandedYet6 分钟前
银河麒麟高级服务器V10(ARM)安装人大金仓KingbaseES完整教程
运维·kingbase·人大金仓·银河麒麟
随风ada19 分钟前
Windows、macOS、liunx下使用qemu搭建riscv64/linux
linux·windows·ubuntu·macos·golang·qemu·risc-v
会是上一次40 分钟前
Lvs集群搭建
运维·服务器·网络
为什么要内卷,摆烂不香吗1 小时前
超简单linux上部署Apache
linux·运维·网络·apache
摸鱼仙人~2 小时前
代理式变革:AI驱动的产品开发与DevOps战略指南
运维·人工智能·devops
白鸽梦游指南2 小时前
LVS运行原理及实战模拟
服务器·网络·lvs
丨千纸鹤丨2 小时前
LVS集群
服务器·网络·lvs
apihz3 小时前
全球天气预报5天(经纬度版)免费API接口教程
android·服务器·开发语言·c#·腾讯云
wydxry3 小时前
在断网情况下,网线直接连接 Windows 笔记本和 Ubuntu 服务器进行数据传输
运维·docker·容器
智象科技3 小时前
智象科技赋能金融、证券行业 IT 运维
大数据·运维·网络·数据库·科技·金融·智能运维