HOME
BLOG
linux多线程
May 22 2023

抽空复习总结了一下笔记

元婴期

线程:

在操作系统中,.rodata是数据段(Data Segment)的一个子段,它通常被用来存储只读的数据,例如字符串常量、全局常量等等。.rodata是英文”read-only data”的缩写,也就是只读数据的意思。

数据段是程序中用来存储静态数据和全局变量的内存区域。数据段通常被分为多个子段,每个子段存储不同类型的数据。除了.rodata之外,数据段还包括.data.bss等子段。

  • .data子段用于存储已初始化的全局变量和静态变量。在程序加载时,.data子段中的数据会被拷贝到内存中的数据段中,并分配相应的内存空间。
  • .bss子段用于存储未初始化的全局变量和静态变量或者初始化为0的。在程序加载时,.bss子段中的变量会被初始化为0,并分配相应的内存空间。由于.bss子段中的变量都被初始化为0,因此不需要在可执行文件中存储它们的初始值,这可以减小可执行文件的大小。

因此,.rodata是数据段的一部分,用于存储只读的数据,例如字符串常量、全局常量等等。.data.bss也是数据段的子段,分别用于存储已初始化和未初始化的全局变量和静态变量。

由于同一进城的多个线程共享同一地址空间,因此text段,data段都是共享的,如果定义一个函数,在各个线程中都可以调用,如果定义一个全局变量,在各个线程中都可以访问到,除此以下也是共享的

  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户id和组id
  5. 堆空间(因为共享进程地址空间)

不共享的:

  1. 线程id
  2. 上下文,包括各种寄存器的值,程序计数器,栈指针
  3. 栈空间
  4. errno变量
  5. 信号屏蔽字
  6. 调度优先级

之前不知道这个:一个线程可以调用pthread_cancel终止同一进程中的另一个线程

pthread_create 传的函数类型是 void* ()(void),即函数应该接收一个void参数,返回一个void指针

pthread_self拿到线程id

锁都是共享,创建的话就放在全局

pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);

有关挂起等待,唤醒等待线程的操作

在 pthread 头文件中,定义了一些宏(宏定义)来简化互斥锁和条件变量的初始化。这些宏可以用于快速创建 pthread_mutex_t 类型的互斥锁和 pthread_cond_t 类型的条件变量,常见的宏定义有以下两种:

  1. PTHREAD_MUTEX_INITIALIZER

这个宏可以用来静态地初始化一个互斥锁。

示例:

pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
  1. PTHREAD_COND_INITIALIZER

这个宏可以用来静态地初始化一个条件变量。

示例:

pthread_cond_t myCondVar = PTHREAD_COND_INITIALIZER;

需要注意的是,使用这些宏定义初始化互斥锁和条件变量时,不能在初始化后使用 pthread_mutex_init() 和 pthread_cond_init() 函数对其进行初始化。

使用这些宏定义仅仅可以用于静态初始化,即这些互斥锁和条件变量的生命周期与程序的生命周期一样长。如果需要在运行时动态创建锁或条件变量,仍然需要使用相应的 pthread 函数进行初始化。

条件变量总是要跟互斥锁搭配用

进到wait说明拿到锁了,但是wait阻塞就说明条件没成立,所以wait会把锁给释放了,别人去抢,别人抢到干完活使得你的条件满足了,别人内部调用唤醒你,此时别人把锁释放把你唤醒,触发你等待的条件,你条件满足了,拿到锁,wait不再阻塞继续执行你的任务

流程如下:

pthread 条件变量是一种同步机制,通常用于在多个线程之间进行通信和控制。条件变量通常用于通知其他线程某个特定的事件已经发生,从而触发一些操作。

pthread 条件变量的过程通常以上述步骤进行:

  1. 定义条件变量和互斥锁

在使用条件变量时,首先要定义一个互斥锁和一个条件变量。

pthread_mutex_t myMutex;
pthread_cond_t myCondVar;
  1. 初始化条件变量和互斥锁

在使用之前,需要使用 pthread_mutex_init() 函数和 pthread_cond_init() 函数对条件变量和互斥锁进行初始化。

pthread_mutex_init(&myMutex, NULL);
pthread_cond_init(&myCondVar, NULL);
  1. 进入临界区

在要等待特定事件的线程进入临界区时,需要先加锁,这样可以确保只有一个线程能够访问共享资源。

pthread_mutex_lock(&myMutex);
  1. 检查条件并等待

接下来,线程应该检查它等待的条件是否已经满足,如果满足,则跳过等待,离开临界区。如果不满足,线程则应该调用 pthread_cond_wait() 函数等待通知。

while (!condition) {
    pthread_cond_wait(&myCondVar, &myMutex);
}

当线程调用 pthread_cond_wait() 函数时,它会释放互斥锁并等待条件变量。如果另一个线程使用 pthread_cond_signal() 函数或 pthread_cond_broadcast() 函数唤醒了等待的线程,则该线程就会重新获得互斥锁并继续执行。

需要注意,在取消等待的情况下可能会产生死锁,因此在使用 pthread_cond_wait() 函数时需要确保计算所有可能的退出情况。

  1. 离开临界区

当等待线程收到通知时,它应该离开等待循环并立即再次锁定互斥锁。在重新锁定互斥锁之前,条件变量的状态可以更改,因此需要重新检查条件变量并跳过等待循环,如果在等待期间已经满足条件变量,则跳过等待循环。

if (condition) {
    // 条件满足,跳过等待循环
} else {
    // 继续等待
}
pthread_mutex_unlock(&myMutex);
  1. 销毁条件变量和互斥锁

在使用完条件变量和互斥锁之后,应该使用 pthread_cond_destroy() 函数和 pthread_mutex_destroy() 函数进行销毁。

pthread_cond_destroy(&myCondVar);
pthread_mutex_destroy(&myMutex);

写个链表,生产者消费者demo

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

typedef struct Node
{
    int val;
    struct Node* next;
}Node;
/*
c语言这么写出错:
原因:Node* next 中的 Node 不是一个已经定义的类型,而是相对于 struct Node 的一个未知类型的指针
typedef struct Node
{
    int val;
    Node* next; ***这里
}Node;
c语言得指明struct,c++不用,C并不支持和C++一样的自动搜寻结构体定义的语法
*/
Node* head=NULL;
//pthread_cond_t mutex = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex;  //可以直接用宏 ,主函数就不用init了,信号量也是如此
pthread_cond_t hasnode;

void * producer(void* arg)
{
    Node* pre;
    while(1)
    {
        pre=(Node*)malloc(sizeof(Node));
        pre->val=rand()%100;
        //头插
        pthread_mutex_lock(&mutex);
        pre->next=head;
        head=pre;
        
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&hasnode);
        printf("produce %d\n",pre->val);
        sleep(rand()%3);
    }
}

void* concumer(void* arg)
{
    Node* pre;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(!head)
        {
            pthread_cond_wait(&hasnode, &mutex);
        }
        pre=head;
        head=head->next;
        pthread_mutex_unlock(&mutex);

        printf("concumer %d\n",pre->val);
        free(pre);
        sleep(rand()%3);
    }
}

int main()
{
    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&hasnode, NULL);
    printf("start!!\n");

    pthread_t pid,cid;
    pthread_create(&pid,NULL,producer,NULL);
    pthread_create(&cid,NULL,concumer,NULL);

    pthread_join(pid,NULL);
    pthread_join(cid,NULL);
    
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&hasnode);
    printf("over!!\n");
    return 0;
}

信号量

生产者消费者的demo:

注意拿信号量要在拿锁之前,想想为什么?

我这篇博客有讲: 信号量解决生产者消费者/读写者问题_右大臣的博客-CSDN博客

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
 
// 链表的节点
struct Node
{
    int number;
    struct Node* next;
};
 
// 生产者线程信号量
sem_t psem;
// 消费者线程信号量
sem_t csem;
 
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;
 
// 生产者的回调函数
void* producer(void* arg)
{
    // 一直生产
    while(1)
    {
        // 生产者拿一个信号灯
        sem_wait(&psem);
        // 加锁, 这句代码放到 sem_wait()上边, 有可能会造成死锁
        pthread_mutex_lock(&mutex);
        // 创建一个链表的新节点
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
        // 节点初始化
        pnew->number = rand() % 1000;
        // 节点的连接, 添加到链表的头部, 新节点就新的头结点
        pnew->next = head;
        // head指针前移
        head = pnew;
        printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
        pthread_mutex_unlock(&mutex);
 
        // 通知消费者消费
        sem_post(&csem);
        
        // 生产慢一点
        sleep(rand() % 3);
    }
    return NULL;
}
 
// 消费者的回调函数
void* consumer(void* arg)
{
    while(1)
    {
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        struct Node* pnode = head;
        printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
        head  = pnode->next;
        // 取出链表的头结点, 将其删除
        free(pnode);
        pthread_mutex_unlock(&mutex);
        // 通知生产者生成, 给生产者加信号灯
        sem_post(&psem);
 
        sleep(rand() % 3);
    }
    return NULL;
}
 
int main()
{
    // 初始化信号量
    sem_init(&psem, 0, 5);  // 生成者线程一共有5个信号灯
    sem_init(&csem, 0, 0);  // 消费者线程一共有0个信号灯
    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);
 
    // 创建5个生产者, 5个消费者
    pthread_t ptid[5];
    pthread_t ctid[5];
    for(int i=0; i<5; ++i)
    {
        pthread_create(&ptid[i], NULL, producer, NULL);
    }
 
    for(int i=0; i<5; ++i)
    {
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }
 
    // 释放资源
    for(int i=0; i<5; ++i)
    {
        pthread_join(ptid[i], NULL);
    }
 
    for(int i=0; i<5; ++i)
    {
        pthread_join(ctid[i], NULL);
    }
 
    sem_destroy(&psem);
    sem_destroy(&csem);
    pthread_mutex_destroy(&mutex);
 
    return 0;
}
 
C进阶