一 源码地址:GitHub - apache/incubator-brpc: brpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".
二 BRPC 双buffer
解析:brpc源码解析(十九)—— 双buffer数据结构DoublyBufferedData详解_wxj1992的博客-CSDN博客_双buffer机制
pthread_key_t : 线程私有存储空间--pthread_key_t_鱼思故渊的博客-CSDN博客_pthread_key_t
因为系统对每一个进程中pthread_key_t类型的个数是有限制的,所以进程中并不能创建无限个的pthread_key_t变量。Linux中能够通过PTHREAD_KEY_MAX(定义于limits.h文件里)或者系统调用sysconf(_SC_THREAD_KEYS_MAX)来确定当前系统最多支持多少个键。Linux中默认是1024个键。这对于大多数程序来说已经足够了。假设一个线程中有多个线程局部存储变量,通常能够将这些变量封装到一个数据结构中。然后使封装后的数据结构与一个线程局部变量相关联,这样就能降低对键值的使用。
三 BRPC负载均衡
1 前奏
(1)Locality-aware load balancing
incubator-brpc/lalb.md at master · apache/incubator-brpc · GitHub
thread-local 锁 c++ 线程局部变量thread_local_糖梨的博客-CSDN博客_c++ 线程变量
(2)一致性哈希
incubator-brpc/consistent_hashing.md at master · apache/incubator-brpc · GitHub
(3)socket概念
并发连接数:一个并发连接数代表一个TCP连接(不一定是tcp)。
一个socket也代表一个进程到进程的连接(不一定是TCP)。套接字Socket=(IP地址:端口号)
brpc中ServerId
// Representing a server inside LoadBalancer.
struct ServerId {
ServerId() : id(0) {}
explicit ServerId(SocketId id_in) : id(id_in) {}
ServerId(SocketId id_in, const std::string& tag_in)
: id(id_in), tag(tag_in) {}
SocketId id;
std::string tag;
};
// Representing a server inside a NamingService.
struct ServerNode {
ServerNode() {}
explicit ServerNode(const butil::EndPoint& pt) : addr(pt) {}
ServerNode(butil::ip_t ip, int port, const std::string& tag2)
: addr(ip, port), tag(tag2) {}
ServerNode(const butil::EndPoint& pt, const std::string& tag2)
: addr(pt), tag(tag2) {}
ServerNode(butil::ip_t ip, int port) : addr(ip, port) {}
butil::EndPoint addr;
std::string tag;
};
// ipv4 + port
struct EndPoint {
EndPoint() : ip(IP_ANY), port(0) {}
EndPoint(ip_t ip2, int port2) : ip(ip2), port(port2) {}
explicit EndPoint(const sockaddr_in& in)
: ip(in.sin_addr), port(ntohs(in.sin_port)) {}
ip_t ip;
int port;
};
每个地址可以附带一个tag,在常见的命名服务中,如果地址后有空格,则空格之后的内容均为tag。 相同的地址配合不同的tag被认为是不同的实例,brpc会建立不同的连接。用户可利用这个特性更灵活地控制与单个地址的连接方式。 如果你需要"带权重的轮询",你应当优先考虑使用wrr算法,而不是用tag来模拟。
2 负载均衡策略
1 RoundRobinLoadBalancer
(This LoadBalancer selects server evenly. Selected numbers of servers(added at the same time) are very close.)
SelectServer的核心处理过程,首先如果recover已开启且判断该请求被拒绝就直接返回拒绝,然后确定或获取offset和步长,然后用offset+步长 / n的方法获取节点下标,并用下标更新offset,然后判断节点是否是最后一个节点/节点不是被过滤掉&&节点可以简单访问,如果获取到了节点,就更新offset和步长(存储在tls中),否则(所有节点不可用)&打开recover模式,就StartRecover
2 RandomizedLoadBalancer
(This LoadBalancer selects servers randomly using a thread-specific random number. Selected numbers of servers(added at the same time) are less close than RoundRobinLoadBalancer.)
首先如果recover已开启且判断该请求被拒绝就直接返回拒绝,然后随机生成一个在n范围内的数字,作为offset,然后获取server,然后判断节点是否是最后一个节点/节点不是被过滤掉&&节点可以简单访问,如果获取到了节点就返回。没有获取到就random一个stride,用offset+步长 / n的方法更新offset(If `Address' failed, use `offset+stride' to retry so that this failed server won't be visited again inside for)。否则(所有节点不可用)&打开recover模式,就StartRecover
3 WeightedRoundRobinLoadBalancer
(This LoadBalancer selects server as the assigned weight. Weight is got from tag of ServerId.)
Server除了包含socket_id,还包含节点的weight
Servers除了包含server列表,还包含各节点weight之和
TLS包含当前的节点的下标,步长(这里的步长不是指节点个数,而是权重),当前节点,所有节点个数,所有节点权重和,以及函数IsNeededCaculateNewStride判断是否需要计算新步长
有一个写死的步长列表(都是素数)
根据权重之和和节点个数来确定合适的步长的方法:1 权重之和为1的话,直接返回1 2 首先算出平均权重,然后从素数步长列表中选择出第一个大于等于平均权重的步长,直到和权重和为互质数,return *iter > weight_sum ? *iter % weight_sum : *iter;
GetServerInNextStride:获取写一次轮询的节点,
(1)uint64_t stride = tls.stride;
Server& remain = tls.remain_server;
(2)如果当前节点剩余权重还有且当前节点不被过滤,那么首先更新当前节点为final_server,如果当前节点剩余权重大于步长,更新权重=权重-步长,直接返回当前节点。否则更新局部步长=原步长-weight。
如果当前节点被过滤或者当前节点虽没有被过滤但是剩余权重被用完:首先更新权重为0,然后++tls.position
(3)while(步长>0) {
final_server = server_list[tls.position].id;
如果节点被过滤,++tls.position;
不被过滤,获取当前节点的weight,如果权重大于步长,更新当前节点及剩余权重(权重-步长),返回当前节点。否则更新临时步长-=weight。++tls.position;
}
SelectServer:
(1)如果tls.IsNeededCaculateNewStride,如果步长是0,首先更新position(随机)。然后根据当前节点个数和权重和来计算新的合适的步长。
(2)如果当前节点权重大于0且当前节点socket_id与server中相应position位置的socket_id不同,当前节点已经被删除,更新权重为0
(3)uint64_t remain_weight = s->weight_sum;
size_t remain_servers = s->server_list.size();
(4)while(remain_servers > 0){
GetServerInNextStride获取轮询的节点
如果这个节点未被过滤且可以简单访问到,更新tls的remain_server和position,并返回
如果这个节点被过滤或者访问不到,跳过这个节点并重新计算步长,--remain_servers filter.emplace(server_id);更新remain_weight,
// Select from begining status.
tls_temp.stride = GetStride(remain_weight, remain_servers);
tls_temp.position = tls.position;
tls_temp.remain_server = tls.remain_server;
}
4 WeightedRandomizedLoadBalancer
This LoadBalancer selects server as the assigned weight. Weight is got from tag of ServerId.
Server包含节点的权重,以及当前节点列表中该节点及之前节点的权重和
server_compare:比较节点的大小的函数,比较维度是当前节点列表中该节点及之前节点的权重和,按照该维度大小升序排序
SelectServer:
遍历每一个节点
(1)生成(0,所有节点权重和)之间的随机值
(2)以该随机值生成一个server
(3)从server列表中选择第一个大于等于第二步生成的server的server(比较维度就是权重和,函数就是server_compare)
(4)如果是最后一个节点或该节点不被过滤 且该节点可以访问得到,那么就返回该节点。
5 ConsistentHashingLoadBalancer
生成哈希的三种方式:
(1)MURMUR3
(2)MD5
(3)KETAMA
有2类ReplicaPolicy(2个接口,第一个是返回哈希方法的名字,第二个是build方法)
(1)DefaultReplicaPolicy
Build函数主要用于为某一个ServerId生成N个分片Node,哈希值是hash(remote_host_n, size).哈希方法是md5或MURMUR3
(2)KetamaReplicaPolicy
返回的哈希名字为“ketama”
Build函数,num_replicas一定要是4的倍数,遍历1-(num_replicas/4),先生成md5(remote_host_n, size),然后用某种奇怪的方法一次生成4个节点
InitReplicaPolicy
生成三个ReplicaPolicy,放在一个数组中
GetReplicaPolicy
根据type获取某一个ReplicaPolicy
ConsistentHashingLoadBalancer有2个基本参数,一个是分片,一个是ConsistentHashingLoadBalancerType
AddBatch函数只是重新置bg数组的大小而已,没有更改bg. 最后返回差值????????????????????????????
AddServer函数,先根据分批数和哈希type生成分片数个节点,排序这些节点,然后_db_hash_ring.ModifyWithForeground(AddBatch, add_nodes, &executed);
AddServersInBatch函数,根据节点数和分片数,挨个节点生成多个分片,然后sort所有节点所有分片,_db_hash_ring.ModifyWithForeground(AddBatch, add_nodes, &executed);
RemoveBatch函数修改了bg数组,bg数组存放最新的server,最后返回差值,每个节点都会删去一批(有分片)
Remove函数,有修改bg数组,会删除一批(分片)
RemoveServer函数:_db_hash_ring.ModifyWithForeground(Remove, server, &executed);
RemoveServersInBatch函数: _db_hash_ring.ModifyWithForeground(RemoveBatch, servers, &executed);
GetLoads函数,获取每个节点(分片后)分散在unit32区间的概率
重头戏:SelectServer
(1)SelectIn需要设置request_code:Controller.set_request_code() is required
(2)request_code要小于等于UINT_MAX
(3)在node数组中找到大于等于request_code的第一个节点为选中的节点,(如果是最后一个节点就换成第一个)如果是最后一个节点且没被排除且可以访问,就返回
6 DynPartLoadBalancer
7 LocalityAwareLoadBalancer
四 BRPC命名服务
1 前奏
client端
1 channel: 连接一台服务器
这类Init连接的服务器往往有固定的ip地址,不需要命名服务和负载均衡,创建起来相对轻量。
2 channel: 连接服务集群
这类Channel需要定期从naming_service_url指定的命名服务中获得服务器列表,并通过load_balancer_name指定的负载均衡算法选择出一台机器发送请求。
3 命名服务
当然在具体实现上,上游会记录每一台下游机器,并定期向命名服务请求或被推送最新的列表,以避免在RPC请求时才去访问命名服务。使用命名服务一般不会对访问性能造成影响,对命名服务的压力也很小。
naming_service_url的一般形式是"protocol://service_name"
- 地址后出现的非注释内容被认为是tag,由一个或多个空格与前面的地址分隔,相同的地址+不同的tag被认为是不同的实例。???????
consul://<service-name>????服务降级???DNS????
命名服务中负载均衡的健康检查????
如果你需要"带权重的轮询",你应当优先考虑使用wrr算法,而不是用tag来模拟。
当命名服务获得机器列表后,可以自定义一个过滤器进行筛选,最后把结果传递给负载均衡:
2 具体实现类
UML类图
NamingServiceActions(虚基类) : 不需要实现,主要是用于添加/删除/重置下游服务节点。
NamingService(虚基类) : 映射一个服务命名到一堆节点。主要有2个方法:
(1)RunNamingService:主要用于周期性或事件驱动的方式来获取服务命名所对应的下游节点,然后调用NamingServiceActions中的方法去告知rpc系统节点的变化。该方法默认会以一个专门的线程来运行,无需其他线程访问,因此实现不需要是线程安全的。
(2)RunNamingServiceReturnsQuickly:RunNamingServiceReturnsQuickly方法默认返回false,当该方法返回为true的时候,RunNamingService不会以专门的线程来运行,所以可能会阻塞当前线程。通常适用于RunNamingService静态且简单的实现,节省单独创建线程所造成的开销。通常RunNamingService方法不会退出从而阻塞当前线程,所以采用一个专门的线程是必须的。
还有一个New方法(虚方法):,返回NamingService*,创建和销毁一个实例,调用者必须调用destroy函数去销毁实例。
NamingServiceFilter(虚基类)
Accept:返回ture代表把某一节点作为候选节点,false代表把某一节点过滤掉。
3 命名服务种类
(1)PeriodicNamingService
继承自NamingService
有虚函数GetServers和非虚函数RunNamingService。通过ns_access_interval(5s)来指定周期间隔。
RunNamingService内部是个死循环(bthread_usleep 5s),先调用GetServers,如果成功,让actions调用ResetServers重置server,如果失败且是第一次调用GetServers,清空servers然后让actions调用ResetServers重置server,然后唤醒WaitForFirstBatchOfServers。
(2)BaiduNamingService
继承自PeriodicNamingService
实现GetServers和New,复用PeriodicNamingService中的RunNamingService
GetServers主要用于实现单次获取Servers
(3)FileNamingService
继承自NamingService,把ConsulNamingService作为友元类
重写RunNamingService和New,新增GetServers
GetServers中service_name是一个文件名,从文件读取可用的ip_port和tag
RunNamingService中用到了FileWatcher的概念,循环前先用service_name初始化FileWatcher。
走进循环,先直接获取servers,然后actions调用ResetServers。然后还有一个死循环,每隔100ms检查文件内容是否有变,如果有变(>1),立即跳出该死循环,继续循环阅读文件。
关于FileWatcher
enum Change {
DELETED = -1,
UNCHANGED = 0,
UPDATED = 1,
CREATED = 2,
};
init/init_from_not_exist:以文件为参数。
check_and_consume:检查和消费被监视文件的变化。 如果它不为 NULL,则写入 `last_timestamp’。返回Change
restore:设置内部时间戳。 用户可以使用此方法使 check_and_consume() 重播更改。
如果文件更新太频繁,由于 stat(2) 和文件系统的精度,此方法可能会返回 UNCHANGED。 如果文件的创建和删除过于频繁,则可能无法检测到该事件。
stat来获取文件最新的状态
(4)ConsulNamingService
继承自NamingService
Consul一词怎么理解
重写RunNamingService和New,新增GetServers和DegradeToOtherServiceIfNeeded
RunNamingService:是一个无限循环,每次循环调用GetServers,如果成功,让actions调用ResetServers重置server,如果失败且是第一次调用GetServers,清空servers然后让actions调用ResetServers重置server,然后唤醒WaitForFirstBatchOfServers。
GetServers:通过rr负载均衡的方式调用http服务来获取可用节点列表,如果过程失败,会降级为FileNamingService,是否支持降级和降级文件路径是通过gflag来控制的。
DegradeToOtherServiceIfNeeded:具体来执行降级操作,调用FileNamingService中的GetServers。
(5)ListNamingService
继承自NamingService
重写RunNamingService,New和RunNamingServiceReturnsQuickly,增加GetServers
RunNamingService不是一个无限循环,只执行一次,对应RunNamingServiceReturnsQuickly函数返回true(不必在新线程中执行,直接阻塞当前线程。)
(6)DomainListNamingService
继承自PeriodicNamingService
重写GetServers和New,GetServers写法与ListNamingService中写法一致。
(7)RemoteFileNamingService
继承自PeriodicNamingService
重写GetServers和New
GetServers:通过指定协议和域名以rr的方式获取某一路径下的数据,然后解析循环解析数据即获取servers
(8)DomainNamingService
继承自PeriodicNamingService
重写GetServers和New
GetServers:通过解析域名来获取一批ip和一个端口,然后组成servers
gethostbyname()函数说明 - hapus - 博客园 gethostbyname
DiscoveryNamingService???????
NamingServiceWatcher(虚基类)
具有不同标签的相同 SocketId 被视为不同的条目。
当您更改服务器的标签时,带有旧标签的服务器将首先出现在 OnRemovedServers 中,然后出现在带有新标签的 OnAddedServers 中。
结构体GetNamingServiceThreadOptions
包含ChannelSignature(不同的签名意味着 Channel 需要单独的套接字)和指向SocketSSLContext的共享指针。
NamingServiceThread(将名称映射到 ServerIds 的专用线程)
包含
1 结构体ServerNodeWithId(有变量ServerNode/SocketId)
2 Actions(继承自NamingServiceActions),重写添加/删除/重置服务节点,并添加WaitForFirstBatchOfServers和EndWait接口,把NamingServiceThread
指针作为owenr对象。
有Start/WaitForFirstBatchOfServers/AddWatcher/RemoveWatcher/Run等函数
也要NamingService*,Actions,std::map<NamingServiceWatcher*, const NamingServiceFilter*>等类型变量。
额外不属于此类的函数
GetNamingServiceThread
获取与 `url' 关联的 decated 线程并将线程放入
`ns_thread'。 使用相同的“url”共享并返回相同的线程。 如果之前没有访问过该 url,则此函数将阻塞,直到
NamingService 返回第一批服务器。 如果没有可用的服务器,除非 `options->succeed_without_server' 打开,此函数返回 -1。
NSKey(协议/服务名字/ChannelSignature)
NSKeyHasher
NamingServiceMap
SocketMapKey(ServerNode,ChannelSignature)
Actions析构函数:Remove all sockets from SocketMap。AddServers&RemoveServers直接abort
Actions::ResetServers
(1)diff servers
首先unique传入的servers,然后set_difference(servers, last_servers)获取新加的servers和socks,然后set_difference( last_servers,servers)获取待删除的servers和socks
(2)Refresh sockets
删除socket:如果待删除socket为空,_sockets = _owner->_last_sockets。否则,先sort _removed_sockets, 然后set_difference(_last_sockets,_removed_sockets)
添加socket: 如果待添加不为空,先sort _added_sockets,然后对_sockets先后insert和inplace_merge。
注意这里的last_servers和last_socket等才是需要最后更新的,代表最后的server和socket。
更新last_servers,last_socket。
遍历每个_watcher,然后先后执行OnRemovedServers和OnAddedServers
然后移除 SocketMapKey.peer 中所有具有相同地址的 Socket
NamingServiceThread析构函数:
(1)Remove from g_nsthread_map first
(2)销毁_tid
(3)循环每一个watcher,然后删除所有节点(OnRemovedServers),然后清空_watchers
(4)销毁NamingService
NamingServiceThread:Start
(1)根据入参初始化内部变量,并清空_last_sockets
(2)如果RunNamingServiceReturnsQuickly返回false,执行RunThis->Run。否则bthread_start_urgent(RunThis->Run)
(3)最后返回NamingServiceThread::WaitForFirstBatchOfServers
NamingServiceThread:WaitForFirstBatchOfServers
(1)首先调用action的WaitForFirstBatchOfServers(会返回_wait_error)
(2)如果上步返回ENODATA并且succeed_without_server:如果log_succeed_without_server,打印告警日志。设置rc=0
(3)如果rc>0,返回-1,并报错。否则返回0
NamingServiceThread:AddWatcher
_watchers map新增watcher,同时新增的watcher调OnAddedServers(都是_last_sockets转换而来的ServerId)
NamingServiceThread:RemoveWatcher
_watchers map删除watcher,(不调用 watcher 的 OnRemovedServers,因为 watcher 可以自己移除套接字,并且在大多数情况下,移除套接字是没有用的。)
NamingServiceThread: Run
_ns调RunNamingService,如果失败,_actions调EndWait。
不要在此处删除可能仍被观察者使用的服务器:停止更新的命名服务并不意味着不再需要它。在dtor内删除servers
ParseNamingServiceUrl
拆分url中的protocol和service_nam
GetNamingServiceThread(是入口)
(1)调ParseNamingServiceUrl获取url中的协议和服务名。
(2)通过NamingServiceExtension用协议获取NamingService实例
(3)检测有无NamingServiceMap,如果没有,创建一个,然后初始化size(64)
(4)从NamingServiceMap获取相应key的NamingServiceThread*:
1 获取不为空指针,但是如果AddRefManually为0,说明改线程正在被销毁,置null
2. 获取为空指针或者被置为空指针,new一个NamingServiceThread,放到map中,然后设置new_thread为true
(5)如果是新线程,nsthread调用Start,如果失败,删除map中对应的key。
如果不是新线程,nsthread调用WaitForFirstBatchOfServers
初使入口是channel的Init函数(仅支持通过命名服务和负载均衡来获取具体实例的init方法)
将此channel连接到一组服务器,其地址可以根据其协议通过 `naming_service_url' 访问。 使用 `load_balancer_name' 指定的方法将流量分配到服务器。
Init函数:
(1)首先如果lb_name为空,退化为连接单个server
(2)GlobalInitializeOrDie注册全局所有实例(NamingServiceExtension()->RegisterOrDie)
(3)初始化InitChannelOptions
(4)ParseURL:拆解出协议/服务名字/port
(5)new一个LoadBalancerWithNaming
(6)构造GetNamingServiceThreadOptions。ns_opt
(7)LoadBalancerWithNaming调用init,把上步构造的ns_opt作为参数之一。
LoadBalancerWithNaming:继承自NamingServiceWatcher
包含唯一变量intrusive_ptr<NamingServiceThread>
重写函数OnAddedServers和OnRemovedServers,分别调用AddServersInBatch和RemoveServersInBatch,添加/删除一堆servers给lb
析构函数,_nsthread_ptr调用RemoveWatcher
init():
(1)SharedLoadBalancer::Init(lb)
(2)GetNamingServiceThread
(3)_nsthread_ptr->AddWatcher
GlobalInitializeOrDie
Extension类
用于定义一个全局字典,将一个字符串映射为对应的用户扩展的实例,主要用于命名服务和负载均衡中,使得初始化的实例全局可用。
Register/RegisterOrDie(失败直接exit(1))/Find
参数值为 1 的 exit 函数用于表示失败终止。某些返回成功调用状态代码值的函数可以与验证返回值并在发生错误时退出程序的条件语句结合使用。请注意,exit(1) 与调用 exit(EXIT_FAILURE) 相同。
每个naming_service都有一个名字,通过Extension类实例化命名服务后可以管理这些naming_service,Extension同样也可以管理负载均衡。
NamingServiceThread类中bthread相关:
bthread_id_trylock
bthread_id_unlock_and_destroy
bthread_id_join
bthread_id_create
bthread_stop
bthread_join
bthread_start_urgent
INVALID_BTHREAD_ID
bthread_t类型变量
详见百度脑图:https://naotu.baidu.com/file/857de5a44fa77e3afbede0a0fb9cbde9
4 bthread相关
id.h
typedef struct {
uint64_t value;
} bthread_id_t;
创建 64 位标识符的函数,这些标识符可以附加数据并锁定而不会出现 ABA 问题。 可以同时从多个线程调用所有函数。 请注意,bthread_id_t 旨在管理对象上的一系列非激烈竞争操作。它比互斥锁慢,不适合一般同步。
1 int bthread_id_create(
bthread_id_t* id, void* data,
int (*on_error)(bthread_id_t id, void* data, int error_code));
创建一个 bthread_id_t 并将其放入 *id。 'id' 为 NULL 时崩溃。 id->value 永远不会为零。
`on_error' 将在 bthread_id_error() 被调用之后被调用。用户必须在 on_error 中调用 bthread_id_unlock() 或 bthread_id_unlock_and_destroy()。
2 #define bthread_id_error(id, err) \
bthread_id_error_verbose(id, err, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__))
int bthread_id_error_verbose(bthread_id_t id, int error_code,
const char *location);
向“id”发出错误。 如果 `id' 没有锁定,锁定 id 并立即运行 `on_error'。
否则 `on_error' 将在 `id' 被解锁之前调用错误。 如果 `id' 被销毁,未调用的 on_error 将被丢弃。 成功返回 0,否则返回错误代码。
3 int bthread_id_unlock(bthread_id_t id);
解锁“身份证”。 必须在成功调用 bthread_id_trylock() 或 bthread_id_lock() 后调用。 成功返回 0,否则返回错误代码。
4 int bthread_id_unlock_and_destroy(bthread_id_t id);
解锁并销毁“id”。 阻塞在 bthread_id_join() 或 bthread_id_lock() 上的服务员将被唤醒。
必须在成功调用 bthread_id_trylock() 或 bthread_id_lock() 后调用。 成功返回 0,否则返回错误代码。
5 int bthread_id_join(bthread_id_t id);
Wait until `id' being destroyed. Waiting on a destroyed bthread_id_t returns immediately. Returns 0 on success, error code otherwise.
6 int bthread_id_cancel(bthread_id_t id);
销毁一个已创建但从未使用过的 bthread_id_t。成功返回 0,否则返回 EINVAL。
7 int bthread_id_trylock(bthread_id_t id, void** pdata);
尝试锁定“id”(用于独占使用数据)成功时返回 0 并使用“data”参数设置“pdata”为 bthread_id_create[_ranged],EBUSY 已锁定,否则为错误代码。
8 #define bthread_id_lock(id, pdata) \
bthread_id_lock_verbose(id, pdata, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__))
int bthread_id_lock_verbose(bthread_id_t id, void** pdata,
const char *location);
锁定“id”(用于独占使用数据)。 如果 `id' 被其他人锁定,请等到 `id' 被解锁或销毁。
成功返回 0 并使用 `data' 参数将 `pdata' 设置为 bthread_id_create[_ranged],否则返回错误代码。
9 int bthread_id_unlock(bthread_id_t id);
解锁“身份证”。 必须在成功调用 bthread_id_trylock() 或 bthread_id_lock() 后调用。 成功返回 0,否则返回错误代码。
10 int bthread_id_unlock_and_destroy(bthread_id_t id);
解锁并销毁“id”。 阻塞在 bthread_id_join() 或 bthread_id_lock() 上的服务员将被唤醒。
必须在成功调用 bthread_id_trylock() 或 bthread_id_lock() 后调用。 成功返回 0,否则返回错误代码。
bthread.h
typedef uint64_t bthread_t;
1 extern int bthread_join(bthread_t bt, void** bthread_return);
使调用线程等待 bthread 'bt' 的终止。 如果 `bt' 已经终止,则立即返回。
- 所有 bthreads 都是“分离的”,但仍然可以连接。
- *bthread_return 始终设置为空。 如果您需要从 bthread 返回值,请通过创建 bthread 的 `args' 传递值。
- bthread_join() 不受 bthread_interrupt 影响。
成功返回 0,否则返回 errno。
2 extern int bthread_stop(bthread_t tid);
使 bthread 上的 bthread_stopped() 返回 true 并中断 bthread。
请注意,当前的 bthread_stop() 仅设置内置的“停止标志”并调用 bthread_interrupt(),这与早期版本的 bthread 不同,并且可以由用户定义的停止标志加上对 bthread_interrupt() 的调用来替换。 成功返回 0,否则返回 errno。
3 extern int bthread_start_urgent(bthread_t* __restrict tid,
const bthread_attr_t* __restrict attr,
void * (*fn)(void*),
void* __restrict args);
创建具有属性“attr”的bthread“fn(args)”并将标识符放入“tid”。
切换到新线程并安排旧线程运行。 当新线程比较紧急时使用此功能。 成功返回 0,否则返回 errno。