抽空复习总结了一下笔记
元婴期
线程:
在操作系统中,.rodata
是数据段(Data Segment)的一个子段,它通常被用来存储只读的数据,例如字符串常量、全局常量等等。.rodata
是英文”read-only data”的缩写,也就是只读数据的意思。
数据段是程序中用来存储静态数据和全局变量的内存区域。数据段通常被分为多个子段,每个子段存储不同类型的数据。除了.rodata
之外,数据段还包括.data
和.bss
等子段。
.data
子段用于存储已初始化的全局变量和静态变量。在程序加载时,.data
子段中的数据会被拷贝到内存中的数据段中,并分配相应的内存空间。.bss
子段用于存储未初始化的全局变量和静态变量或者初始化为0的。在程序加载时,.bss
子段中的变量会被初始化为0,并分配相应的内存空间。由于.bss
子段中的变量都被初始化为0,因此不需要在可执行文件中存储它们的初始值,这可以减小可执行文件的大小。
因此,.rodata
是数据段的一部分,用于存储只读的数据,例如字符串常量、全局常量等等。.data
和.bss
也是数据段的子段,分别用于存储已初始化和未初始化的全局变量和静态变量。
由于同一进城的多个线程共享同一地址空间,因此text段,data段都是共享的,如果定义一个函数,在各个线程中都可以调用,如果定义一个全局变量,在各个线程中都可以访问到,除此以下也是共享的
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户id和组id
- 堆空间(因为共享进程地址空间)
不共享的:
- 线程id
- 上下文,包括各种寄存器的值,程序计数器,栈指针
- 栈空间
- errno变量
- 信号屏蔽字
- 调度优先级
之前不知道这个:一个线程可以调用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 类型的条件变量,常见的宏定义有以下两种:
- PTHREAD_MUTEX_INITIALIZER
这个宏可以用来静态地初始化一个互斥锁。
示例:
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
- PTHREAD_COND_INITIALIZER
这个宏可以用来静态地初始化一个条件变量。
示例:
pthread_cond_t myCondVar = PTHREAD_COND_INITIALIZER;
需要注意的是,使用这些宏定义初始化互斥锁和条件变量时,不能在初始化后使用 pthread_mutex_init() 和 pthread_cond_init() 函数对其进行初始化。
使用这些宏定义仅仅可以用于静态初始化,即这些互斥锁和条件变量的生命周期与程序的生命周期一样长。如果需要在运行时动态创建锁或条件变量,仍然需要使用相应的 pthread 函数进行初始化。
条件变量总是要跟互斥锁搭配用
进到wait说明拿到锁了,但是wait阻塞就说明条件没成立,所以wait会把锁给释放了,别人去抢,别人抢到干完活使得你的条件满足了,别人内部调用唤醒你,此时别人把锁释放把你唤醒,触发你等待的条件,你条件满足了,拿到锁,wait不再阻塞继续执行你的任务
流程如下:
pthread 条件变量是一种同步机制,通常用于在多个线程之间进行通信和控制。条件变量通常用于通知其他线程某个特定的事件已经发生,从而触发一些操作。
pthread 条件变量的过程通常以上述步骤进行:
- 定义条件变量和互斥锁
在使用条件变量时,首先要定义一个互斥锁和一个条件变量。
pthread_mutex_t myMutex;
pthread_cond_t myCondVar;
- 初始化条件变量和互斥锁
在使用之前,需要使用 pthread_mutex_init() 函数和 pthread_cond_init() 函数对条件变量和互斥锁进行初始化。
pthread_mutex_init(&myMutex, NULL);
pthread_cond_init(&myCondVar, NULL);
- 进入临界区
在要等待特定事件的线程进入临界区时,需要先加锁,这样可以确保只有一个线程能够访问共享资源。
pthread_mutex_lock(&myMutex);
- 检查条件并等待
接下来,线程应该检查它等待的条件是否已经满足,如果满足,则跳过等待,离开临界区。如果不满足,线程则应该调用 pthread_cond_wait() 函数等待通知。
while (!condition) {
pthread_cond_wait(&myCondVar, &myMutex);
}
当线程调用 pthread_cond_wait() 函数时,它会释放互斥锁并等待条件变量。如果另一个线程使用 pthread_cond_signal() 函数或 pthread_cond_broadcast() 函数唤醒了等待的线程,则该线程就会重新获得互斥锁并继续执行。
需要注意,在取消等待的情况下可能会产生死锁,因此在使用 pthread_cond_wait() 函数时需要确保计算所有可能的退出情况。
- 离开临界区
当等待线程收到通知时,它应该离开等待循环并立即再次锁定互斥锁。在重新锁定互斥锁之前,条件变量的状态可以更改,因此需要重新检查条件变量并跳过等待循环,如果在等待期间已经满足条件变量,则跳过等待循环。
if (condition) {
// 条件满足,跳过等待循环
} else {
// 继续等待
}
pthread_mutex_unlock(&myMutex);
- 销毁条件变量和互斥锁
在使用完条件变量和互斥锁之后,应该使用 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;
}