信号量 – 线程级
信号量类似于一个特殊的计数器,当信号量值大于零,表示可以访问的临界资源的个数,等于零,对信号量执行P操作会阻塞
对信号量的P,V操作不一定非得在同一线程中
信号量方法:
#include <semaphore.h>
信号量类型:sem_t 全局定义一个sem_t类型的信号量
初始化:int sem_init(sem_t *sem, int shared, int init_val);
shared:设置信号量是否在进程间共享,Linux不支持,一般给零
init_val:信号量的初始值
P操作:int sem_wait(sem_t *sem);
V操作:int sem_post(sem_t *sem);
销毁:int sem_destroy(sem_t *sem);
示例:主线程 函数线程
主线程获取用户输入,函数线程将用户输入的数据存储到a.txt中。
1.主线程获取的用户数据如何传递给函数线程:全局 堆区 栈区(空间指针需要传入才可共享)
2.函数线程只能在主线程获取到数据(直接存储在共享空间中),才能执行write操作
3.主线程只能在函数线程将数据写入文件后才能获取下一次数据
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <fcntl.h>
#include <signal.h>
#include <semaphore.h>
#include <pthread.h>
#define BUFFSIZE 128
sem_t sem1,sem2;
void *fun(void *arg)
{
char *buff = (char *)arg;
int fw = open("a.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
assert(fw != -1);
while(1)
{
sem_wait(&sem1);
if(strncmp(buff,"end",3) == 0)
break;
write(fw,buff,strlen(buff));
memset(buff,0,BUFFSIZE);
sem_post(&sem2);
}
}
int main()
{
sem_init(&sem1,0,0);
sem_init(&sem2,0,2);
char buff[BUFFSIZE] = {0};
pthread_t id;
int res = pthread_create(&id,NULL,fun,(void *)buff);
assert(res == 0);
while(1)
{
sem_wait(&sem2);
printf("please input: ");
fgets(buff,BUFFSIZE,stdin);
sem_post(&sem1);
if(strncmp(buff,"end",3) == 0)
break;
}
pthread_join(id,NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
exit(0);
}
执行结果:
条件变量
为多个线程提供一个汇合的场所
wait再加锁状态下,将线程添加到条件变量对应的等待队列中,然后执行解锁操作 – 》将线程添加到队列与唤醒是互斥的
(用互斥锁保护,假如同时有两个线程,一个进行添加,一个进行唤醒,如果没有互斥锁保护,就不知道到底是添加还是唤醒,加上互斥锁,就是在添加的时候不能唤醒,唤醒的时候不能添加,更安全)
读写锁
读写锁有三种状态:读模式下加锁,写模式下加锁,不加锁,一次只能有一个线程占有写模式读写锁,但是多个线程可以同时占有读模式读写锁
如果当前是读加锁,另外一个线程试图写加锁请求,那么之后的读加锁都会阻塞,这样可以避免读模式长期被占用,写加锁请求却不能得到满足
线程安全
线程是并发运行的,而且线程之间共享进程的地址空间,所以在执行时,可能会修改共享数据,修改的时机也无法确定,所以最后的结果有不确定性
1.使用线程同步
2.不使用共享数据 – 》共享数据转化为非共享数据
不能保证线程安全的库函数:
(方法种使用了全局或者数据可以共享的变量,所以不安全)
1.多线程种某个线程调用fork(),子进程会有和父进程相同数量的进程嘛?
多线程种一个线程调用fork创建子进程,子进程种只有fork这个线程会被启动运行,其他线程不会运行。
2.父进程被加锁的互斥锁fork后在子进程中是否已经加锁
多线程种调用fork创建子进程,子进程会继承父进程的锁的状态,父进程中所有的对锁的的操作都不能影响子进程的锁的状态。就会出现死锁,所以在多线程中执行fork之前,需要调用pthread_atfork方法,保证继承的锁绝对是解锁状态
pthread_atfork是一个注册方法,注册了三个函数,这三个函数在不同时间执行
atfork注册的三个方法,以及其调用时机,都是为了保证在fork执行过程中,所有的锁都是枷锁状态,没有其他线程会加锁成功。fork完成之后,分别在父进程空间和子进程空间解锁,fork之后,父进程的使用的锁都是解锁状态的