多线程2 - 线程的同步和互斥
目录
引入
-- 发现有时候结果为 5w 有时候小于 5w
这是线程在同时使用共享资源时 发生的抢占问题
我们需要使用线程的同步和互斥来解决这个问题
实现线程对资源的独占
-- 实现线程的同步和互斥一共有三种方式
- 1 锁
- 2 信号量
- 3 条件变量
互斥锁
-- 互斥锁可以实现线程对资源的独占
使用锁的步骤
1、创建锁 初始化一把锁
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
-- 使用函数创建为动态的创建互斥锁
- int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t *restrict attr);
-- 静态的创建锁
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
是将PTHREAD_MUTEX_INITIALIZER赋值给定义的互斥量
但这个方法没办法设置互斥量的属性,也不适用于动态分配的互斥量,比较少用。
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,互斥锁的初始化。
-- 函数的参数:
- pthread_mutex_t * mutex:填写要进行初始化的锁的对象
例如:pthread_mutex_t lock; 该参数填写:&lock - pthread_mutexattr_t *restrict attr:初始化的锁的属性,给 NULL 则使用默认的互斥锁属性,默认属性为快速互斥锁。
互斥锁的属性在创建锁的时候指定,在 LinuxThreads 实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
-- 函数返回值:
- pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
- 函数成功执行后,互斥锁被初始化为未锁住态。
-- 不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。
2、加锁
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_mutex_lock(pthread_mutex_t *mutex);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的互斥量加锁。
对参数所填写的锁 进行上锁操作
锁处于解锁状态 会上锁成功 函数不会阻塞
锁处于上锁状态 会阻塞等待锁解锁 然后再进行上锁操作解除阻塞
-- 函数的参数:
- pthread_mutex_t * mutex:填写要进行加锁的锁的对象
例如:pthread_mutex_t lock; 该参数填写:&lock
-- 函数返回值:
- 成功返回零
- 失败返回一个 非零值
3、解锁
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的互斥量解锁。
注意对一把锁进行加锁后 一定要对其进行解锁操作 否则可能出现死锁的情况
-- 函数的参数:
- pthread_mutex_t * mutex:填写要进行解锁的锁的对象
例如:pthread_mutex_t lock; 该参数填写:&lock
-- 函数返回值:成功返回零
- 失败返回一个 非零值
4、尝试上锁函数
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的互斥量尝试加锁。
对参数所填写的锁 进行上锁操作
锁处于解锁状态 会上锁成功 函数不会阻塞
锁处于上锁状态 该函数不会阻塞 会立马返回一个值 16 -- EBUSY
-- 函数的参数:
- pthread_mutex_t * mutex:填写要进行加锁的锁的对象
例如:pthread_mutex_t lock; 该参数填写:&lock
-- 函数返回值:
- 成功返回零
- 失败返回16
5、销毁锁
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于销毁指定的互斥量。
当我们不在需要锁的时候 需要对锁进行销毁 锁会失去原本的作用
-- 函数的参数:
- 成功返回0
- 失败返回一个非零值
1、使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无须销毁。
2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。
3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。
-- 这里注册函数就有了意义,如果在上锁完,还没有进行解锁,线程就结束的话,可以利用注册函数,在注册函数中写解锁的操作,这样就算线程结束,锁也会被解锁。
信号量
-- linux系统中提供了两个信号量实现,一种是System V信号量,另一种是POSIX信号量,它们的作用是相同的,都是用于同步进程之间及线程之间的操作,以达到无冲突地访问共享资源的目的。
-- 今天讲的(也就是下午所讲的)就是无名信号量
-- 无名信号量, 又称为基于内存的信号量,由于其没有名字,没法通过open操作直接找到对应的信号量,所以很难直接用于没有关联的两个进程之间。
无名信号量多用于线程之间的同步。因为线程会共享地址空间, 所以访问共同的无名信号量是很容易办到的事情
1、初始化一个信号量
-- 函数头文件
- #include <semaphore.h>
-- 函数的原型:
- int sem_init(sem_t *sem, int pshared, unsigned int value);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于初始化一个信号量。
-- 函数的参数:
- sem_t *sem:填写要进行初始化的信号量对象
例如:sem_t sem; 该参数填写:&sem - int pshared:
如果 pshared 的值为 0,那么信号量在线程的地址空间内;(表示在线程间使用)否则该信号量是进程间共享的。 - unsigned int value:信号量的初始值。
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
2、P操作 -- 对信号量进行消耗
-- 函数头文件
- #include <semaphore.h>
-- 函数的原型:
- int sem_wait(sem_t *sem);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行P操作。
对信号量进行消耗操作 -1
信号量的值>0 会对信号量进行-1 操作
信号量的值=0 会阻塞直到该信号量可以进行-1 操作
-- 函数的参数:
- sem_t *sem:填写要进行P操作的信号量对象
例如:sem_t sem; 该参数填写:&sem
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
3、V操作 -- 对信号量进行还原
-- 函数头文件
- #include <semaphore.h>
-- 函数的原型:
- int sem_post(sem_t *sem);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行V操作。
还原一个信号量 进行+1 操作
-- 函数的参数:
- sem_t *sem:填写要进行V操作的信号量对象
例如:sem_t sem; 该参数填写:&sem
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
4、尝试进行抢占信号量操作
-- 函数头文件
- #include <semaphore.h>
-- 函数的原型:
- int sem_trywait(sem_t *sem);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行尝试抢占操作。
当信号量的值>0 进行-1 操作 当信号量的值=0 时 该函数会立马返回
5、销毁信号量
-- 函数头文件
- #include <semaphore.h>
-- 函数的原型:
- int sem_destroy(sem_t *sem);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于销毁指定的信号量。
当我们不再使用信号量时 会对信号量进行摧毁操作
-- 函数的参数:
- sem_t *sem:填写要进行销毁的信号量对象
例如:sem_t sem; 该参数填写:&sem
-- 函数返回值:
- 成功返回 0
- 失败返回 -1
条件变量
-- 多线程编程中,涉及到线程并发,因此也衍生了一些问题,常见的有2类问题:
1、多个线程访问同一个共享资源,这个可以用互斥量来解决;
2、某些线程运行后,有时需要等待一定的条件才会继续执行,如果条件不满足就会等待,而条件的达成, 很可能取决于另一个线程。
-- 条件变量主要是解决上面的第二个问题。条件变量用来阻塞一个线程,直到某条件满足为止。通常条件变量和互斥锁同时使用。
-- 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
-- 为什么要用条件变量?
- 因为如果不使用条件变量,线程就需要 轮询+休眠 来查看是否满足条件,这样严重影响效率。
1、初始化条件变量
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于初始化一个条件变量。
-- 函数的参数:
-
pthread_cond_t *cond:填写要进行初始化的条件变量对象
例如:pthread_cond_t cond; 该参数填写:&cond -
const pthread_condattr_t *attr:填写条件变量的属性,如果为NULL,则使用默认属性。
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
-- 也可以使用定义初始化条件变量
- pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2、等待条件变量
-- 让指定的条件变量进入等待状态,其工作机制是先解锁传入的互斥量,再让条件变量等待,从而使所在线程处于阻塞状态。函数返回时,系统会确保该线程再次持有互斥量(加锁)。
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
-- 函数的作用
- 阻塞当前线程 等待其他线程发送条件变量
-- 函数的参数:
-
pthread_cond_t *cond:填写要进行等待的条件变量对象
例如:pthread_cond_t cond; 该参数填写:&cond -
pthread_mutex_t *mutex:填写互斥锁对象
例如:pthread_mutex_t mutex; 该参数填写:&mutex
该锁的作用:用来确保只有当前线程正在使用共享资源
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
-- pthread_cond_wait函数返回时,系统会确保该线程再次持有互斥量(加锁)。
3、发送条件变量
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_cond_signal(pthread_cond_t *cond);
-- 函数的作用
- 告知等待条件变量的线程 条件已满足 可以解除阻塞继续向下运行
-- 函数的参数:
- pthread_cond_t *cond:填写要进行发送的条件变量对象
例如:pthread_cond_t cond; 该参数填写:&cond
-- 函数返回值:
- 成功返回0
- 失败返回一个非零值
4、销毁条件变量
-- 函数头文件
- #include <pthread.h>
-- 函数的原型:
- int pthread_cond_destroy(pthread_cond_t *cond);
-- 函数的作用
- 该函数用于 C 函数的多线程编程中,用于销毁指定的条件变量。
-- 函数的参数:
- pthread_cond_t *cond:填写要进行销毁的条件变量对象
例如:pthread_cond_t cond; 该参数填写:&cond
-- 函数返回值:
- 成功返回 0
- 失败返回 -1
1、使用PTHREAD_COND_INITIALIZE静态初始化的条件变量,不需要被销毁。
2、要调用pthread_cond_destroy销毁的条件变量可以调用pthread_cond_init重新进行初始化。
3、不要引用已经销毁的条件变量, 这种行为是未定义的。
-- 使用条件变量等待时,会使用一个互斥量进行加锁,加锁的临界区包含了“①查看是否满足条件、②调用pthread_cond_wait进入条件等待”这两个操作。
#include "stdio.h"
#include "pthread.h"
#include "string.h"
#include "unistd.h"
char subway[20] = {0};
pthread_mutex_t lock;
pthread_cond_t cond;
void * func(void *arg)
{
while(1)
{
printf("地铁到达郑州大学\n");
pthread_mutex_lock(&lock);
strcpy(subway,"郑州大学站");
pthread_mutex_unlock(&lock);
sleep(2);
printf("地铁到达梧桐街\n");
pthread_mutex_lock(&lock);
strcpy(subway,"梧桐街");
//发送条件变量
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
sleep(5);
printf("地铁到达五一公园\n");
pthread_mutex_lock(&lock);
strcpy(subway,"五一公园");
pthread_mutex_unlock(&lock);
sleep(3);
}
}
int main()
{
//初始化锁
pthread_mutex_init(&lock,NULL);
//初始化条件变量
pthread_cond_init(&cond,NULL);
pthread_t id;
pthread_create(&id,NULL,func,NULL);
printf("\t还没到6点\n");
sleep(8);
printf("\t下班\n");
sleep(2);
printf("\t到达梧桐街地铁站\n");
pthread_mutex_lock(&lock);
if(strcmp(subway,"梧桐街") == 0)
{
printf("\t上车成功\n");
//pthread_mutex_unlock(&lock);
}
else
{
printf("\t等地铁ing\n");
//等待条件变量
pthread_cond_wait(&cond,&lock);
printf("\t地铁到达上车!!!\n");
}
pthread_mutex_unlock(&lock);
pthread_cond_destroy(&cond);
while(1)
{}
return 0;
}