Linux线程同步的互斥锁

先决条件: C语言中的多线程

null

线程同步 定义为一种机制,确保两个或多个并发进程或线程不会同时执行某些特定的程序段,称为临界段。进程对关键部分的访问是通过使用同步技术来控制的。当一个线程开始执行 临界截面 (程序的序列化段)另一个线程应该等到第一个线程完成。如果不采用适当的同步技术,可能会导致 竞赛条件 变量的值可能是不可预测的,并且取决于进程或线程的上下文切换的计时。

线程同步问题 研究同步问题的示例代码:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
void * trythis( void * arg)
{
unsigned long i = 0;
counter += 1;
printf ( " Job %d has started" , counter);
for (i = 0; i < (0xFFFFFFFF); i++)
;
printf ( " Job %d has finished" , counter);
return NULL;
}
int main( void )
{
int i = 0;
int error;
while (i < 2) {
error = pthread_create(&(tid[i]), NULL, &trythis, NULL);
if (error != 0)
printf ( "Thread can't be created : [%s]" , strerror (error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}


如何编译上述程序? 要使用gcc编译多线程程序,我们需要将其与pthreads库链接。以下是用于编译程序的命令。

gfg@ubuntu:~/$ gcc filename.c -lpthread

在本例中,创建了两个线程(作业),在这些线程的start函数中,维护了一个计数器,以获取关于作业编号的日志,该作业编号已启动,以及何时完成。

输出:

Job 1 has started
Job 2 has started
Job 2 has finished
Job 2 has finished

问题: 从最后两个日志可以看出,日志 作业2已经完成 “在没有日志的情况下重复两次” 作业1已完成 “我看见了。

为什么会这样? 通过仔细观察和可视化代码的执行,我们可以看到:

  • 原木 工作2已经开始了 “刚刚打印出来” 工作1已经开始了 ‘因此很容易得出结论,当线程1正在处理时,调度程序调度了线程2。
  • 如果我们假设上述假设成立,那么 柜台 ‘变量在作业1完成之前再次递增。
  • 所以,当作业1实际完成时,计数器的错误值产生了日志 作业2已经完成 “然后是” 作业2已经完成 ‘用于实际作业2,反之亦然,因为它依赖于调度程序。
  • 所以我们发现问题不在于重复日志,而在于“计数器”变量的错误值。
  • 实际问题是,当第一个线程正在使用或即将使用变量“counter”时,第二个线程使用了该变量。
  • 换句话说,我们可以说,在使用共享资源“计数器”时,线程之间缺乏同步导致了问题,或者一句话,我们可以说这个问题是由于两个线程之间的“同步问题”造成的。

    如何解决?

    实现线程同步最常用的方法是使用 互斥量 .

    互斥

    • 互斥锁是我们在使用共享资源之前设置的锁,在使用后释放。
    • 设置锁后,其他线程无法访问代码的锁定区域。
    • 所以我们看到,即使线程2被调度,而线程1没有访问共享资源,并且代码被线程1使用互斥锁锁定,那么线程2甚至无法访问该代码区域。
    • 因此,这确保了代码中共享资源的同步访问。

    互斥操作

    1. 假设一个线程使用互斥锁锁定了一个代码区域,并正在执行该代码段。
    2. 现在,如果调度器决定进行上下文切换,那么准备执行同一区域的所有其他线程都将被解锁。
    3. 所有线程中只有一个线程可以执行,但如果该线程尝试执行已锁定的同一代码区域,那么它将再次进入睡眠状态。
    4. 上下文切换将一次又一次地进行,但在释放代码上的互斥锁之前,任何线程都无法执行代码的锁定区域。
    5. 互斥锁将仅由锁定它的线程释放。
    6. 因此,这确保了一旦一个线程锁定了一段代码,那么其他线程就不能执行相同的区域,直到锁定它的线程解锁它。

    因此,该系统在处理共享资源时确保线程之间的同步。

    互斥锁被初始化,然后通过调用以下两个函数实现锁定: 第一个函数初始化互斥锁,通过第二个函数可以锁定代码中的任何关键区域。

    1. int pthread_mutex_init(pthread_mutex_t*restrict mutex,const pthread_mutextatr_t*restrict attr): 创建一个由互斥体引用的互斥体,其属性由attr指定。如果attr为NULL,则使用默认的互斥属性(非递归)。

      返回值 如果成功,pthread_mutex_init()将返回0,互斥锁的状态将被初始化并解锁。 如果失败,pthread_mutex_init()将返回-1。

    2. int pthread_mutex_lock(pthread_mutex_t*mutex): 锁定一个互斥对象,该对象标识互斥对象。如果互斥锁已被另一个线程锁定,该线程将等待互斥锁可用。锁定互斥锁的线程将成为其当前所有者,并在同一线程解锁互斥锁之前一直保持所有者身份。当互斥锁具有递归属性时,锁的使用可能会有所不同。当这种互斥锁被同一个线程多次锁定时,计数就会增加,不会发布等待的线程。拥有线程的线程必须调用相同次数的pthread_mutex_unlock(),以将计数减为零。

      返回值 如果成功,pthread_mutex_lock()将返回0。 如果失败,pthread_mutex_lock()将返回-1。

通过调用以下两个函数,可以解锁和销毁互斥锁: 第一个函数释放锁,第二个函数销毁锁,以便将来不能在任何地方使用。

  1. int pthread_mutex_unlock(pthread_mutex_t*mutex): 释放互斥对象。如果一个或多个线程正在等待锁定互斥对象,pthread_mutex_unlock()会导致其中一个线程从pthread_mutex_lock()返回并获取互斥对象。如果没有线程在等待互斥锁,互斥锁将在没有当前所有者的情况下解锁。当互斥锁具有递归属性时,锁的使用可能会有所不同。当这种互斥锁被同一个线程多次锁定时,unlock将减少计数,并且不会发布等待线程继续使用锁运行。如果计数减为零,则释放互斥锁,如果有线程正在等待,则发布互斥锁。

    返回值 如果成功,pthread_mutex_unlock()将返回0。 如果失败,pthread_mutex_unlock()将返回-1

  2. int pthread_mutex_destroy(pthread_mutex_t*mutex): 删除标识互斥对象的互斥对象。互斥锁用于保护共享资源。互斥被设置为无效值,但可以使用pthread_mutex_init()重新初始化。

    返回值 如果成功,pthread_mutex_destroy()将返回0。 如果失败,pthread_mutex_destroy()将返回-1。

mutex 一个演示如何使用互斥锁进行线程同步的示例

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
pthread_mutex_t lock;
void * trythis( void * arg)
{
pthread_mutex_lock(&lock);
unsigned long i = 0;
counter += 1;
printf ( " Job %d has started" , counter);
for (i = 0; i < (0xFFFFFFFF); i++)
;
printf ( " Job %d has finished" , counter);
pthread_mutex_unlock(&lock);
return NULL;
}
int main( void )
{
int i = 0;
int error;
if (pthread_mutex_init(&lock, NULL) != 0) {
printf ( " mutex init has failed" );
return 1;
}
while (i < 2) {
error = pthread_create(&(tid[i]),
NULL,
&trythis, NULL);
if (error != 0)
printf ( "Thread can't be created :[%s]" ,
strerror (error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&lock);
return 0;
}


在上述代码中:

  • 互斥锁在主函数的开头初始化。
  • 在使用共享资源“计数器”时,相同的互斥锁被锁定在“trythis()”函数中。
  • 在函数“trythis()”的末尾,相同的互斥锁被解锁。
  • 在主函数的末尾,当两个线程都完成时,互斥锁被销毁。

输出:

Job 1 started
Job 1 finished
Job 2 started
Job 2 finished

因此,这一次两个作业的开始和完成日志都存在。所以线程同步是通过使用互斥来实现的。

参考资料: 同步(计算机科学) 洛克(计算机科学)

本文由 基什莱·维尔马 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以使用 贡献极客。组织 或者把你的文章寄到contribute@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。

如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享