Linux —— 线程同步

Linux —— 线程同步

  • 死锁
  • 线程同步
    • 条件变量
    • pthread_cond_wait
    • pthread_cond_signal
      • 初始状态
      • 为什么之后会“阻塞”
      • 如何修改以持续运行
    • pthread_cond_broadcast
  • 条件变量的接口
  • 抢票模拟

我们今天接着来了解线程:

死锁

死锁(Deadlock)是计算机科学中多任务处理和并发控制时的一种现象,特别是在操作系统、数据库系统和网络系统中较为常见。它指的是两个或两个以上的进程(或线程)在执行过程中,因为互相等待对方持有的资源而无法继续执行的情况,从而导致所有的进程都无法向前推进,系统状态僵死

死锁发生的四个必要条件为:

  1. 互斥条件:资源不能被多个进程同时使用,只能由一个进程占有。
  2. 请求与保持条件:已经持有至少一个资源的进程可以再请求新的资源。
  3. 不剥夺条件:已分配给一个进程的资源在该进程未明确释放之前,不能被其他进程强行夺走。
  4. 循环等待条件:存在一种进程资源的循环等待链,链中的每个进程已获得的资源同时被链中下一个进程所请求。

避免死锁的策略通常包括:

  • 破坏四个必要条件之一:例如,通过实施资源分配的排序策略来破坏循环等待条件,或者允许进程在等待新资源时释放已持有的资源以破坏请求与保持条件。
  • 银行家算法:这是一种避免死锁的著名算法,通过预判资源分配请求是否会导致系统进入不安全状态来决定是否分配资源。
  • 死锁检测与恢复:系统定期检查是否存在死锁,一旦检测到死锁,则采取相应措施恢复,比如终止一部分进程,收回资源重新分配。
  • 超时重试:为避免长时间阻塞,可以为资源请求设置超时,超时后回滚并重试请求。

理解并有效管理这些机制对于设计高效、稳定的多线程和分布式系统至关重要。

以我们现在的水平,死锁这个现象并不常见,大家了解一下即可。

线程同步

线程同步是指在多线程编程中,为了防止多个线程访问同一资源造成数据不一致、数据竞争等问题,确保线程按照预定的顺序执行而采取的一系列协调机制。其目的是确保在任一时刻,只有一个线程能够访问共享资源,其他线程则需要等待,直到该资源被释放。

线程同步的方法和机制多种多样,以下是一些常见的同步技术:

  1. 互斥锁(Mutex):最基本的同步原语,用于保护临界区(即访问共享资源的代码段)。当一个线程获得了互斥锁后,其他试图获取同一锁的线程将被阻塞,直到第一个线程释放锁。
  2. 信号量(Semaphore):信号量是一个更通用的同步工具,不仅可以实现互斥锁的功能,还能控制对有限资源的访问数量。信号量维护一个计数器,线程可以增加或减少这个计数器的值,当计数器为非正时,试图减少它的线程会被阻塞。
  3. 条件变量(Condition Variable):条件变量用于线程间的同步,允许一个或多个线程等待某个特定条件发生。当条件不满足时,线程可以阻塞自己,直到另一个线程通知条件已满足。
  4. 读写锁(Read-Write Lock):适用于读操作远多于写操作的场景。允许多个读者同时访问资源,但写者访问时会排斥所有其他读写者。这提高了并发性能。
  5. 原子操作(Atomic Operations):提供了一种方法来执行简单操作(如增加、减少、交换等),这些操作在多线程环境中被视为不可分割的,即操作过程中不会被其他线程打断。
  6. 屏障(Barrier):屏障是一个同步点,所有线程到达这个点后才会继续执行。这对于需要所有线程完成某阶段工作后再一起进入下一阶段的场景非常有用。

正确地使用线程同步机制是编写并发程序的关键,能够有效地避免竞态条件、死锁等问题,保证程序的正确性和一致性。

我们实现同步,一般会用条件变量

条件变量

条件变量是多线程编程中用于线程同步的一种机制,它允许线程在某些条件未满足时等待,直到其他线程改变了这个条件后再唤醒它们。条件变量通常与互斥锁一起使用,以防止多个线程同时修改共享数据,并且确保在条件不满足时线程能够安全地等待

在使用条件变量的经典模式中,涉及以下几个步骤:

  1. 加锁:在检查或修改共享数据之前,线程首先需要获取一个关联的互斥锁,以确保独占访问。
  2. 检查条件:线程检查某个条件是否满足。如果条件已经满足,线程可以继续执行。如果没有满足,则进入下一步。
  3. 等待:如果条件没有满足,线程调用条件变量的等待函数(如pthread_cond_wait在POSIX线程中)并释放互斥锁。这样做的目的是让出CPU给其他线程,并且让自己处于等待状态,直到被其他线程通过条件变量的信号函数(如pthread_cond_signalpthread_cond_broadcast)唤醒。
  4. 被唤醒后重新检查条件:当线程被唤醒时,它会重新获取互斥锁并再次检查条件是否满足。这是必要的,因为可能存在“虚假唤醒”情况,即线程被唤醒并不是因为预期的条件改变,而是其他原因。
  5. 执行或再次等待:如果条件满足,线程可以继续执行其后续逻辑。如果不满足,线程可能需要再次调用等待函数,重复上述过程。

条件变量的主要目的是提供一种优雅的方式,让线程能够高效地等待某个条件,并且只有当条件变为真时才继续执行,从而实现了线程间的协调和同步。在多线程编程中,正确使用条件变量可以避免数据竞争和竞态条件,提高程序的健壮性。

我们这里举个简单的例子,我们创建三个线程,向屏幕循环打印信息:

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* threadRouite(void* args)
{const char* name = static_cast<const char*>(args);while(true){std::cout << "I am running my name is: "<< name << std::endl;sleep(1);}return nullptr;
}int main()
{//创建线程pthread_t tid1,tid2,tid3; //线程idpthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);
}

在这里插入图片描述
这个时候会有线程竞争的问题,这个时候我们可以上锁,保证在一个时间段内,只有一个线程往屏幕打印。

在这里插入图片描述
这个时候会有一个问题,很长的一段时间都是thread-1运行,线程2,线程3都没有机会执行,为了让三个线程相对均匀的被调动,我们这个时候就要使用条件变量

上完锁之后,我们让所有线程等待:

pthread_cond_wait

#include<iostream>
#include<pthread.h>
#include<unistd.h>//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //void* threadRouite(void* args)
{const char* name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex); //让所有线程等待std::cout << "I am running my name is: "<< name << std::endl;sleep(1);pthread_mutex_unlock(&mutex);}return nullptr;
}int main()
{//创建线程pthread_t tid1,tid2,tid3; //线程idpthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);
}

在这里插入图片描述我们可以等3秒就唤醒一个线程:

pthread_cond_signal

#include<iostream>
#include<pthread.h>
#include<unistd.h>//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //void* threadRouite(void* args)
{const char* name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex); //让所有线程等待std::cout << "I am running my name is: "<< name << std::endl;sleep(1);pthread_mutex_unlock(&mutex);}return nullptr;
}int main()
{//创建线程pthread_t tid1,tid2,tid3; //线程idpthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");//3秒后,唤醒一个线程sleep(1);pthread_cond_signal(&cond);//3秒后,唤醒一个线程sleep(1);pthread_cond_signal(&cond);//3秒后,唤醒一个线程sleep(1);pthread_cond_signal(&cond);pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);
}

在这里插入图片描述
打印三次之后,程序就像是被阻塞了一样,我们来分析一下:

初始状态

  • 创建了三个线程(tid1, tid2, tid3),每个线程启动时会立刻调用pthread_cond_wait(&cond, &mutex),这意味着它们一启动就会因为条件变量cond而阻塞,等待被其他线程通过pthread_cond_signalpthread_cond_broadcast唤醒。
  • 主线程随后执行,它首先等待1秒,然后发送一次信号(pthread_cond_signal(&cond))。这个信号会唤醒一个处于等待状态的线程(假设是tid1),tid1线程开始执行并打印消息,完成后再次调用pthread_cond_wait进入等待状态。
  • 主线程再次等待1秒后,发送第二个信号,唤醒第二个线程(比如tid2)。
  • 同理,第三个信号唤醒了最后一个线程tid3

为什么之后会“阻塞”

  • 在每个线程被唤醒并执行后,它们会再次调用pthread_cond_wait(&cond, &mutex)。此时,由于主线程的循环已经完成,没有更多的pthread_cond_signal调用来唤醒它们。因此,这三个线程都再次进入等待状态,期望着未来某个时刻收到信号。
  • 由于主线程没有进一步的信号发送动作,并且没有设计退出条件或循环来持续发送信号,这三个线程就这样一直等待下去,看起来就像“阻塞”了。

如何修改以持续运行

如果想要线程持续运行并周期性地被唤醒,一种方式是在主线程中加入循环,持续发送信号给条件变量。但是,直接无限制地发送信号可能导致不必要的频繁唤醒和资源消耗,因此应该根据具体需求来设计合理的唤醒逻辑。

#include<iostream>
#include<pthread.h>
#include<unistd.h>//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //void* threadRouite(void* args)
{const char* name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex); //让所有线程等待std::cout << "I am running my name is: "<< name << std::endl;sleep(1);pthread_mutex_unlock(&mutex);}return nullptr;
}int main()
{//创建线程pthread_t tid1,tid2,tid3; //线程idpthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");while(true){sleep(1);pthread_cond_signal(&cond); //一直发送信号}pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);
}

在这里插入图片描述
这样每个线程都会被均匀的调度。

pthread_cond_broadcast

pthread_cond_broadcast会一次唤醒所有线程,至于谁能抢到运行的权利,就看各个线程的本事了:

#include<iostream>
#include<pthread.h>
#include<unistd.h>//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //void* threadRouite(void* args)
{const char* name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex); //让所有线程等待std::cout << "I am running my name is: "<< name << std::endl;sleep(1);pthread_mutex_unlock(&mutex);}return nullptr;
}int main()
{//创建线程pthread_t tid1,tid2,tid3; //线程idpthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");while(true){sleep(1);pthread_cond_broadcast(&cond); //一直发送信号,唤醒所有线程}pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);
}

在这里插入图片描述

条件变量的接口

使用一些接口之后,我们来看看条件变量的接口:
在POSIX线程(pthread)库中,条件变量提供了以下主要接口用于线程间的同步:

  1. 初始化和销毁:

    • 初始化条件变量:
      int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *attr);
      
      cv 是指向条件变量的指针,attr 是可选的条件变量属性(一般传NULL使用默认属性)。
    • 销毁条件变量:
      int pthread_cond_destroy(pthread_cond_t *cv);
      
      释放与条件变量关联的资源。
  2. 等待和唤醒:

    • 等待条件变量:
      int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
      
      当前线程会释放互斥锁mutex并进入等待状态,直到其他线程通过pthread_cond_signalpthread_cond_broadcast唤醒它。被唤醒后,会重新获取锁。
    • 信号通知:
      int pthread_cond_signal(pthread_cond_t *cv);
      
      唤醒一个(至少一个)在条件变量cv上等待的线程。具体唤醒哪个线程由实现决定。
    • 广播通知:
      int pthread_cond_broadcast(pthread_cond_t *cv);
      
      唤醒所有在条件变量cv上等待的线程。这通常用于需要唤醒所有线程的情况。
  3. 属性操作:

    • 初始化属性对象:
      int pthread_condattr_init(pthread_condattr_t *attr);
      
      初始化条件变量属性对象。
    • 设置属性:
      int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
      
      设置条件变量是否在进程间共享。pshared参数为PTHREAD_PROCESS_SHARED表示跨进程共享,PTHREAD_PROCESS_PRIVATE表示仅在同一进程中共享(默认)。
    • 获取属性:
      int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared);
      
      获取条件变量属性中的进程共享状态。
    • 销毁属性对象:
      int pthread_condattr_destroy(pthread_condattr_t *attr);
      
      释放条件变量属性对象占用的资源。

这些接口共同构成了条件变量的核心功能,允许线程之间基于某些条件进行复杂的同步控制,是构建多线程程序中不可或缺的一部分。

抢票模拟

我们这里加上条件变量,实现一个补票的逻辑:

#include <iostream>
#include <pthread.h>
#include <unistd.h>// 初始化互斥锁,用于保护共享资源tickets
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 初始化条件变量,用于线程间通信,通知票已补充
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 共享资源:剩余票数
int tickets = 100; // 初始有100张票// 线程函数,模拟售票操作
void* threadRouite(void* args) {const char* name = static_cast<const char*>(args); // 获取线程名称while(true) { // 无限循环,直到手动中断// 加锁,确保接下来的操作原子性pthread_mutex_lock(&mutex);// 检查是否有票可售if(tickets > 0) {tickets--; // 卖出一张票std::cout << "Left tickets: " << tickets << " thread name: " << name << std::endl; // 打印剩余票数和售票线程名} else {std::cout << "no tickets" << std::endl; // 如果没票,打印提示// 没有票时,线程等待,释放锁,直到被通知pthread_cond_wait(&cond, &mutex);}// 操作完成后解锁pthread_mutex_unlock(&mutex);}return nullptr; // 线程返回指针类型为空指针
}int main() {// 创建三个售票线程pthread_t tid1, tid2, tid3;pthread_create(&tid1, nullptr, threadRouite, (void*)"thread-1"); // 创建并启动线程1pthread_create(&tid2, nullptr, threadRouite, (void*)"thread-2"); // 创建并启动线程2pthread_create(&tid3, nullptr, threadRouite, (void*)"thread-3"); // 创建并启动线程3// 主线程循环,每三秒检查一次是否需要补充票while(true) {sleep(3); // 等待3秒// 加锁,保护票数的修改pthread_mutex_lock(&mutex);// 检查是否需要补充票if(tickets == 0) {tickets += 100; // 补充100张票}// 解锁pthread_mutex_unlock(&mutex);// 通知所有等待的线程,票已补充pthread_cond_broadcast(&cond); // 使用广播,唤醒所有等待线程}pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);return 0; // 程序正常结束
}

这段代码展示了一种简单的线程同步机制,其中售票线程会尝试减少票数,如果没有票则等待;主线程定期检查并补充票数,然后通过条件变量通知等待的线程。然而,正如注释中指出的,pthread_join调用在当前代码结构中无法执行,因为main函数中的无限循环没有提供退出机制。在实际应用中,应当设计合适的逻辑来终止循环,并确保所有线程能够正确地结束。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/13808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基础编程函数题

1.简单输出整数&#xff1a;本题要求实现一个函数&#xff0c;对给定的正整数N&#xff0c;打印从1到N的全部正整数。 #include <stdio.h> void PrintN ( int N ); int main () { int N; scanf("%d", &N); PrintN( N ); return 0; } void Prin…

会所前台装水离子雾化壁炉前和装后对比

会所前台装水离子雾化壁炉前和装后会有明显的对比&#xff1a; 装水离子雾化壁炉之前&#xff1a; 普通前台氛围&#xff1a; 在壁炉安装之前&#xff0c;前台可能显得普通&#xff0c;缺乏独特的装饰元素或焦点。 空间感平淡&#xff1a;前台的氛围可能相对平淡&#xff0c…

Python踩坑系列之使用redis报错:module ‘redis‘ has no attribute ‘Redis‘问题

一步一步往后看哦&#xff01;&#xff01;&#xff01; 纳尼&#xff0c;大伙看看这是什么情况&#xff0c;都是这么写的呢&#xff0c;为啥我这就报错了0.0 出现问题不可怕&#xff0c;解决它就完事了。 方法一、安装redis重新运行程序 pip install redis 无果&#xff0…

金丝雀发布(灰度发布)介绍 及 声明式管理方法简介

目录 一 应用发布策略 1&#xff0c;滚动发布&#xff08;k8s默认&#xff09; 2&#xff0c;蓝绿发布 3&#xff0c;金丝雀发布 二 金丝雀发布&#xff08;Canary Release&#xff09; &#xff08;灰度发布&#xff09; 1&#xff0c;金丝雀发布图解 2&#xff0…

数据库操作(函数)

函数是一段可以直接被另外一段程序调用的程序或代码 一。字符串函数 1.concat(s1,s1....sn)&#xff1a;字符串拼接&#xff0c;将s1&#xff0c;s2&#xff0c;sn拼接为一个字符串 例如&#xff1a; select concat("hello","world"); 2.lower(str&…

登录验证登录次数失败过多进行设置延时

现象结果示例截图 swagger示例效果 控制台示例效果 后端代码示例 package com.java.javamethod.service.impl;import com.java.javamethod.dao.UserMapper; import com.java.javamethod.domain.Result; import com.java.javamethod.domain.User; import lombok.extern.slf4j.…

《绝地潜兵2》开发商目标成为下一个暴雪或FS社

《绝地潜兵2》的开发商Arrowhead正以惊人的表现在游戏界崭露头角。这款游戏在发售后迅速获得了巨大成功&#xff0c;使得Arrowhead的首席创意官Johan Pilestedt怀揣雄心壮志&#xff0c;他们的目标是在保持独立的同时&#xff0c;成为下一个暴雪或From Software。 Johan Pilest…

firewalld

一、Firewalld概述 Firewalld 支持网络区域所定义的网络链接以及接口安全等级的动态防火墙管理工具 支持IPV4、IPV6防火墙设置以及以太网桥 支持服务或应用程序直接添加防火墙规则接口 拥有两种配置模式 运行时配置 永久配置 二、Firewalld和iptables的关系 netfilter 位于L…

10个最佳Android数据恢复工具,用于恢复已删除的文件

由于我们现在在智能手机上存储了许多重要文件&#xff0c;因此了解数据恢复工具变得很重要。您永远不会知道何时需要使用适用于Android的数据恢复工具。 由于不乏Windows数据恢复工具&#xff0c;因此从崩溃的计算机中恢复文件很容易。但是&#xff0c;当涉及到从Android恢复数…

兆原数通基于Apache SeaTunnel的探索实践

随着大数据技术的不断发展&#xff0c;数据同步工具在企业中的应用变得愈发重要。为了满足复杂多样的业务需求&#xff0c;找到一款高效、灵活的数据同步工具变得尤为关键。 在这篇文章中&#xff0c;我们将分享兆原数通研发经理李洪军对Apache SeaTunnel的选择、应用及经验。这…

jinkens打包前端依赖下载失败怎么办

不知道有没有小伙伴遇见这种问题&#xff0c;项目在本地可以正常下载、运行打包&#xff0c;但在jinkens上就不行了&#xff0c;配置了几种镜像也还是不行&#xff0c;这要如何解决呢&#xff1f; 那就只能去到jinkens配置的工作空间那里&#xff0c;找到对应的项目 &#xff…

总结优秀的prompt案例,学习更有效的prompt提示词工程写法,值得收藏

Prompt 提示词工程大多数人都在用&#xff0c;而且都会用&#xff0c;但是不一定写的好&#xff1f;很多人都在想怎么写好&#xff0c;更能满足自己的业务需求&#xff0c;或者实际场景。 我最近工作中也写了很多的prompt&#xff0c;像zero-shot、few-shot、COT这些都尝试过、…

FSC认证是什么?森林认证的好处是什么?

FSC认证&#xff08;Forest Stewardship Council&#xff0c;森林管理委员会认证&#xff09;是一种运用市场机制来促进森林可持续经营&#xff0c;实现生态、社会和经济目标的工具。以下是关于FSC认证的详细介绍&#xff1a; 一、FSC认证包括两个方面&#xff1a; 森林经营认…

乡村振兴与农业现代化:以现代农业科技为引领,提升农业综合生产能力,打造高产高效、生态安全的美丽乡村

目录 一、引言 二、现代农业科技在乡村振兴中的作用 &#xff08;一&#xff09;提高农业生产效率 &#xff08;二&#xff09;促进农业产业升级 &#xff08;三&#xff09;改善农村生态环境 三、提升农业综合生产能力的途径 &#xff08;一&#xff09;加强农业科技研…

Diffusion Policy:基于扩散模型的机器人动作生成策略

项目地址&#xff1a; Diffusion Policy (columbia.edu) 一、摘要 本文介绍了 "扩散策略"&#xff0c;这是一种生成机器人行为的新方法&#xff0c;它将机器人的视觉运动策略&#xff08;visuomotor policy&#xff09;表示为条件去噪扩散过程&#xff08;conditi…

添加、修改和删除列表元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 添加、修改和删除列表元素也称为更新列表。在实际开发时&#xff0c;经常需要对列表进行更新。下面我们介绍如何实现列表元素的添加、修改和删除。 …

如何使用Matlab进行三角剖分(自定义函数实现delaunayTriangulation 使用Bowyer-Watson 算法)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Delaunay三角形 二、使用步骤 1.Bowyer-Watson算法 2.算法步骤 三、动画演示 四、核心代码 五、对比matlab自带函数和我们的算法&#xff1a; 总结 前…

谷歌开源项目BERT源码解读与应用实例

数据及代码见文末 基于BERT的中文情感分析实战:基于BERT的中文情感分析实战-CSDN博客 基于BERT的中文命名实体识别识别实战:基于BERT的中文命名实体识别识别实战-CSDN博客 1.项目配置文件 GLUE/BERT_BASE_DIR是项目的预训练权重,预训练权重主要包含3个部分:参数配置文件…

打气球小游戏

1.气球往上飘 我们声明两个符号常量来作为窗体的长和宽,接着就是常规操作 #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600#include<easyx.h> #include<stdio.h> int main() {initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);setbkcolor(WHITE);cleardevice();get…

python+selenium - UI自动框架之封装浏览器引擎driver方法

在做兼容性测试的适合&#xff0c;可以运行指定的浏览器&#xff08;IE,Chrome,Edge&#xff09; 目录文件&#xff1a; from selenium import webdriver from urllib3.exceptions import ProtocolError from common.log import *def getDriver():# if browserType is None:br…