本文目录
- 前述:同步机制的引入及概念
- 一、线程的互斥锁
- 1. 定义
- 2. 互斥锁常用方法
- 3. 相关函数
- (1)头文件
- (2)创建互斥锁
- (3)销毁互斥锁
- (4)加锁
- (5)解锁
- 4. 使用例程
前述:同步机制的引入及概念
为了更全面的学习,在学习本章之前,请查看《线程的创建与使用》这篇文章。
进程中的多线程之间使用的是共享的数据,那么如果多个线程同时操作同一个共享资源,那么岂不是很混乱了。为了解决这个问题,引入了线程间的同步机制,即每次对共享资源进行操作时,只能有一个线程操作,其他线程必须等待其操作完毕后再进行操作。
线程的同步机制是指在多线程编程中,用来协调线程之间的操作,以确保它们按预期顺序执行,避免竞态条件、死锁和其他并发问题。同步机制的主要目的是管理对共享资源的访问,确保数据一致性和程序的正确性。
序号 | 机制 | 描述 |
---|---|---|
1 | 互斥锁 | 互斥锁的主要作用是同步,确保同一时刻只有一个线程能够进入临界区,从而避免竞态条件。例如,多个线程需要访问或修改同一个共享变量时,可以用互斥锁保护这个变量。 |
2 | 条件变量 | 条件变量用于线程等待某个条件成立,通常与互斥锁结合使用。 |
3 | 信号量/灯 | 信号量是一个计数器,用于控制对资源的访问,允许多个线程同时访问一定数量的资源。 |
一、线程的互斥锁
1. 定义
在 Posix Thread 中定义了一套专门用于线程互斥的 mutex 函数。mutex 是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态(上锁和解锁)。
问题:为什么需要对共享资源加锁?
答:因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,以实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程才能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
注意:要使用互斥锁时,一定要避免死锁的出现!死锁是指两个或多个线程相互等待对方释放资源,从而导致所有线程都无法继续执行。避免死锁是多线程编程中的一个重要问题。且不应该在信号处理函数中使用互斥锁。
2. 互斥锁常用方法
互斥锁类型: ( 1)快速锁(普通锁),(2)嵌套锁(递归锁),(3)检错锁。 这里我们使用最多的就是快速锁,即普通锁。
互斥锁有两种创建(初始化)方法:静态方式(使用宏)和动态方式(用函数创建)。我们常使用动态创建。
3. 相关函数
(1)头文件
#include <pthread.h>
(2)创建互斥锁
互斥锁需要在线程创建之前初始化好,以确保在任何线程尝试使用锁之前,锁已经准备就绪。这样做是为了防止竞争条件和潜在的未定义行为。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
//pthread_mutex_t *mutex :传入互斥锁句柄的地址。
//const pthread_mutexattr_t *mutexattr :填NULL,表示该锁为普通锁。
(3)销毁互斥锁
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//pthread_mutex_t *mutex :传入互斥锁的句柄地址。
(4)加锁
对共享资源进行操作前,需要进行加锁操作。
int pthread_mutex_lock(pthread_mutex_t *mutex)
//pthread_mutex_t *mutex :传入互斥锁的句柄地址。
(5)解锁
对共享资源操作完毕后,需要进行解锁操作,以便其他线程使用这个共享资源。
int pthread_mutex_unlock(pthread_mutex_t *mutex)
//pthread_mutex_t *mutex :传入互斥锁的句柄地址。
4. 使用例程
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t mutex; //互斥锁句柄
int Sum_tick= 10000000; //假如:总票数。(公共资源)利用互斥锁保护。
void *task(void *arg)
{
long index= (long)arg; //用index接收arg的值,区别第几个线程。
int *slef_tick =(int *)malloc(sizeof(int *));
*slef_tick =0; //本窗口售出票数统计
//避免死锁的出现,必须要有解锁动作
while(1)
{
//如果该线程上锁,则其他线程需要等待解锁才能继续执行。
pthread_mutex_lock(&mutex); //上锁:对公共资源使用前
if(Sum_tick > 0) //还有票
{
Sum_tick--;
pthread_mutex_unlock(&mutex); //解锁:对公共资源操作完后
(*slef_tick)++;
}
else
{
pthread_mutex_unlock(&mutex); //解锁: 防止死锁出现,当tick < 0时也需要解锁。
break; //下班
}
}
printf("%ld号窗口今日售出%d张票。\n",index, *slef_tick);
//退出线程并返回slef的数值
pthread_exit((void *)slef_tick);
}
int main(int argc, char **argv)
{
int ret;
pthread_t thread[10];
long i;
int *thread_return;
int sum=0; //sum记录所有子线程返回值的和。
//在线程创建之前创建好互斥锁。 NULL为普通锁
ret=pthread_mutex_init(&mutex, NULL);
if(ret <0)
{
perror("pthread_mutex_init error!\n");
return -1;
}
//创建10个线程:
for(i=0;i<10;i++)
{
ret=pthread_create(&thread[i], NULL, task, (void *)i); //把i的值传给函数func1的形参
if(ret!=0)
{
perror("pthread_create error!\n");
return -1;
}
}
//等待线程释放,并销毁线程资源
for(i=0;i<10;i++)
{
pthread_join(thread[i], (void **)&thread_return);
sum = sum+ (*thread_return);
}
printf("今日景点总售出%d张票。\n",sum);
//销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
为什么每个线程售出的票数都不一样呢?
答:这是因为线程间也存在竞争,谁抢到CPU的使用权谁就开始执行。我们可以查看CPU的核心数来确定每次可以同时有多少个线程同时运行。