线程的同步:
线程间同步(Thread Synchronization)是指在多线程环境下,通过使用信号量(Semaphore)、条件变量(Condition Variable)或其他同步机制,确保多个线程按照一定的顺序执行,以达到协调和合作的目的。同步机制可以用于控制线程的执行顺序、等待其他线程的完成、传递消息等。通过同步机制,可以避免线程之间的竞争条件和数据不一致问题。
信号量的操作:
p操作:sem_wait (sem_t *sem)申请资源,信号量减一
v操作:sem_post()释放资源,信号量加1
PV操作通常用于实现信号量(Semaphore),而信号量是一种用于线程间同步和互斥的机制。
PV操作包括两个基本操作:
1. P操作(也称为wait操作):P操作用于申请资源。当线程执行P操作时,如果资源可用(信号量的值大于0),线程将继续执行,并将信号量的值减1,表示占用了一个资源。如果资源不可用(信号量的值为0),线程将被阻塞,等待资源的释放。
2. V操作(也称为signal操作):V操作用于释放资源。当线程执行V操作时,它将释放一个资源,并将信号量的值加1。如果有其他线程正在等待该资源,其中一个线程将被唤醒,继续执行。
PV操作可以用于解决多线程环境下的资源竞争和互斥访问问题。通过使用信号量和PV操作,可以实现对共享资源的控制和同步,确保线程之间的正确执行顺序和共享资源的一致性。
需要注意的是,PV操作是原子操作,即在执行过程中不会被中断。这确保了线程在执行PV操作时的原子性和互斥性,避免了竞态条件和数据不一致的问题。
理解信号量:信号量与资源的关系
在多线程编程中,信号量是一种用于控制对共享资源访问的同步机制。信号量可以看作是一种计数器,用于表示可用的资源数量。
资源可以是任何需要被线程竞争和访问的共享对象,例如共享内存区域、文件、设备等。在代码中,数组num可以被视为一个共享资源。
信号量的值表示可用的资源数量。当信号量的值大于0时,表示有可用的资源。当信号量的值为0时,表示资源已被占用,其他线程需要等待。
通过使用信号量,可以控制线程对共享资源的访问。当一个线程需要访问共享资源时,它会尝试获取信号量资源(通过调用sem_wait()函数)。如果信号量的值大于0,线程可以获取资源并继续执行。如果信号量的值为0,线程将被阻塞,直到有其他线程释放信号量资源。
当一个线程完成对共享资源的访问时,它需要释放信号量资源,以便其他线程可以获取资源并继续执行(通过调用sem_post()函数)。释放信号量资源会将信号量的值加1,并唤醒等待该信号量的线程。
因此,信号量是一种用于控制对共享资源访问的机制,通过调整信号量的值来控制线程的执行顺序和互斥访问共享资源。
可以理解为只有当信号量资源非0的时候,对应线程才能执行
总结:
sem_wait()函数用于获取信号量资源,如果信号量的值大于0,则将信号量的值减1,并继续执行。如果信号量的值为0,则线程将被阻塞,直到信号量的值大于0为止
sem_post()函数用于释放信号量资源,也称为V操作。它会将信号量的值加1,并唤醒一个等待该信号量的线程(如果有)。
sem_wait()函数用于获取信号量资源,也称为P操作。它会尝试获取信号量,如果信号量的值大于0,则将信号量的值减1,并继续执行。如果信号量的值为0,则线程将被阻塞,直到信号量的值大于0为止。
sem_post()函数用于释放信号量资源,也称为V操作。它会将信号量的值加1,并唤醒一个等待该信号量的线程(如果有)。
在代码中,sem_wait(&sem_sort)用于获取sem_sort信号量资源,以确保在适当的时候执行排序操作。sem_post(&sem_trans)用于释放sem_trans信号量资源,以通知逆序线程可以执行逆序操作。sem_post(&sem_show)用于释放sem_show信号量资源,以通知打印线程可以执行打印操作。
通过使用sem_wait()和sem_post()函数,可以控制线程对共享资源的访问,确保线程按照预期的顺序执行,并避免竞争条件和数据不一致的问题。
1、信号量的设置
首先使用sem_t定义全局信号量变量
然后使用信号量初始化函数设置信号量的值
sem_init函数用于初始化一个命名或未命名的信号量。它的原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
- sem:指向要初始化的信号量的指针。
- pshared:指定信号量的共享方式。如果为0,则信号量只能在同一进程的线程之间共享。如果为非零值,则信号量可以在多个进程之间共享。
- value:指定信号量的初始值。
sem_init函数成功返回0,失败返回-1,并设置errno来指示错误类型。
/*
* _oo0oo_
* o8888888o
* 88" . "88
* (| -_- |)
* 0\ = /0
* ___/`---'\___
* .' \\| |// '.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' |_/ |
* \ .-\__ '-' ___/-. /
* ___'. .' /--.--\ `. .'___
* ."" '< `.___\_<|>_/___.' >' "".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `_. \_ __\ /__ _/ .-` / /
* =====`-.____`.___ \_____/___.-`___.-'=====
* `=---='
*
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 佛祖保佑 永不宕机 永无BUG
*/
// 使用多线程(分别实现对同一数组进行冒泡排序、逆序、打印)保证输出数组要么是升序要么是降序。
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
// 定义信息量全局变量
sem_t sem_sort;
sem_t sem_trans;
sem_t sem_show;
// 排序函数
void *sort(void *arg)
{
int *num = (int *)arg;
// 执行排序
sem_wait(&sem_sort); // 为1表示该资源能被线程使用使用后,其值会减1,为0就表示该该线程暂无资源可以使用,阻塞
for (int i = 0; i < 10 - 1; i++)
{
for (int j = 0; j < 10 - 1 - i; j++)
{
int temp;
if (num[j] > num[j + 1])
{
temp = num[j];
num[i] = num[j + 1];
num[j + 1] = temp;
}
}
}
sem_post(&sem_trans); // 对信号量资源加1,通知trans线程,资源可用
}
// 逆序函数
void *trans(void *arg)
{
int *num = (int *)arg;
sem_wait(&sem_trans);
int i = 0;
int j = 9;
int temp;
while (i < j)
{
temp = num[i];
num[i] = num[j];
num[j] = temp;
i++;
j--;
}
sem_post(&sem_show);
}
// 打印函数
void *show(void *arg)
{
int *num = (int *)arg;
sem_wait(&sem_show);
printf("The array is: ");
for (int i = 0; i < 10; i++)
{
printf("%d ", num[i]);
}
printf("\n");
return NULL;
}
int main()
{
srand(time(NULL));
// 创建一个数组用于存储一个数据,传参的时候把地址传过去
int num[10];
for (int i = 0; i < 10; i++)
{
num[i] = rand() % 100;
}
// 信息量初始化
if (-1 == sem_init(&sem_sort, 0, 1))
{
perror("pthread error");
}
if (-1 == sem_init(&sem_trans, 0, 0))
{
perror("pthread error");
}
if (-1 == sem_init(&sem_show, 0, 0))
{
perror("pthread error");
}
// 线程创建:1、定义线程tid
pthread_t tid_sort;
pthread_t tid_trans;
pthread_t tid_show;
// 开辟线程:
if (0 != pthread_create(&tid_sort, NULL, sort, num))
{
perror("pthread_create error ");
}
if (0 != pthread_create(&tid_trans, NULL, trans, num))
{
perror("pthread_create error ");
}
if (0 != pthread_create(&tid_show, NULL, show, num))
{
perror("pthread_create error ");
}
// 同步线程
if (0 != pthread_join(tid_sort, NULL))
{
perror("jion error");
return -1;
}
if (0 != pthread_join(tid_trans, NULL))
{
perror("jion error");
return -1;
}
if (0 != pthread_join(tid_show, NULL))
{
perror("jion error");
return -1;
}
}
线程间互斥
线程间互斥(Mutual Exclusion)是指在多线程环境下,通过使用互斥锁(Mutex)或其他同步机制,确保同一时间只有一个线程可以访问共享资源。当一个线程获得了互斥锁后,其他线程需要等待,直到该线程释放互斥锁。互斥锁可以防止多个线程同时访问共享资源,从而避免了数据竞争和不一致的问题。
对于线程同步和线程互斥之间的联系:
如果线程按照一定的顺序执行,并且每个线程在访问共享资源时都不会被其他线程中断,那么确实可以避免数据竞争和不一致的问题。然而,在实际的多线程编程中,线程的执行顺序和时间是不确定的,可能会被操作系统的调度器随时中断和调度,这就可能导致数据竞争和不一致的问题。
让我们考虑一个更复杂的例子,假设有一个在线银行应用,有两个线程分别代表两个不同的用户。这两个用户都想从他们共享的银行账户中取款。
假设账户的初始余额为100美元。用户A想取款50美元,用户B想取款70美元。每个取款操作都包括两个步骤:检查余额是否足够,如果足够则减去取款金额。
如果没有使用互斥锁来保护账户余额,可能会出现以下的情况:
1. 用户A的线程检查余额,发现足够。
2. 用户B的线程也检查余额,发现足够。
3. 用户A的线程从账户中取款50美元,余额变为50美元。
4. 用户B的线程也从账户中取款70美元,余额变为-20美元。
在这种情况下,即使每个用户都检查了余额是否足够,但由于没有使用互斥锁来保护账户余额,所以最终的余额变为了负数,这显然是不正确的。
如果使用互斥锁来保护账户余额,那么在任何时候只有一个用户可以访问账户。这样,即使有多个用户需要取款,也不会出现余额不足的问题。
示例代码如下:
pthread_mutex_t mutex; // 定义互斥锁
int balance = 100; // 账户余额
void *withdraw(void *amount)
{
int amt = *(int *)amount;
pthread_mutex_lock(&mutex); // 加锁
if (balance >= amt) {
balance -= amt;
}
pthread_mutex_unlock(&mutex); // 解锁
}
通过这种方式,你可以在已经使用了线程同步的同时,使用线程互斥来保护共享资源,确保数据的一致性。