Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >从boost::lockfree::spsc_queue筛选项

从boost::lockfree::spsc_queue筛选项
EN

Stack Overflow用户
提问于 2016-01-13 04:06:10
回答 1查看 548关注 0票数 1

我使用boost::lockfree::spsc_queue将流数据从线程发送到工作线程。

这些是项目的结构:

代码语言:javascript
运行
AI代码解释
复制
struct spsc_queue_item
{
    uint8_t ID;
    void *data;
};

数据由spsc_queue.push插入,另一个线程通过spsc_queue.pop读取。

但是我也有一些关于工作线程的“命令”。就像ID 0是“启动过滤器”,ID 1是“停止过滤器”,ID 2 id“数据”.

因此,如果许多“数据”被推送到队列中,那么像“停止筛选器”这样的命令将被延迟,因为首先要处理“数据”项。但是,如果命令“停止筛选”出现在“数据”项中,则“数据”项是无用的,可以丢弃。

现在我知道还有成员函数"consume_one“和"consume_all”。

但是,我没有找到一个例子,如何使用函子与这些函数。我的想法是以consume_one为例,首先检查带有ID==0或ID==1的项是否在队列中,然后再继续使用ID==2处理数据项。

有人有一个很小的例子,如何使用函子过滤出发布到请求ID的项目?

或者,是否有其他快速方法可以通过“优先级”标志从队列中获取项?

为了响应sehe的回答,更新了,提供了更多信息:

谢谢你提供这些信息。

知道怎么做才能更好吗?

我需要向工作线程发出信号,例如“启动过滤器”、“停止筛选器”、.

我在考虑使用事件:

代码语言:javascript
运行
AI代码解释
复制
SetEvent(hStartFilter);

但是在这里,我必须为每个命令使用、创建和关闭一个事件。

“数据”也可以有不同的ID。与工作线程一样,线程接收:

代码语言:javascript
运行
AI代码解释
复制
"start filter" with ID=0
"start filter" with ID=1

然后,使用ID0和ID1的“数据”进入队列。现在线程用ID0接收“停止过滤器”。因此,使用ID0的数据队列中的所有项目都是过时的,可以删除。

我的第一个测试是从队列中抓取所有的物品。检查每一个匹配的ID并删除项目。剩下的其他项目将在之后被推回队列。但是,如果有大量不同if的数据(最多32),这将是大量的CPU使用和耗时操作。队列的最大大小是2048项。

还有更好的方法吗?

EN

回答 1

Stack Overflow用户

发布于 2016-01-13 04:16:05

队列具有FIFO语义。没有过滤的方法,除非您只想丢弃不符合您的标准的元素。(只需包装pop函数)

这方面的典型解决方案是使用

  • 优先级队列
  • 两个单独的队列,一个用于数据,一个用于命令。

如果数据队列中不能有超过x个元素,请考虑使用循环缓冲区。spsc_queue使用环形缓冲区作为底层存储。

UPDATE作为对问题编辑的响应,我决定使用每个ID的过滤状态的带外信令创建一个演示。

让我先从通常的定义开始:

代码语言:javascript
运行
AI代码解释
复制
static constexpr uint8_t NUM_SOURCES = 32;

现在,由警察和生产者共同使用的共同定义:

代码语言:javascript
运行
AI代码解释
复制
namespace queueing {
    using data_t = std::vector<char>; // just for demo

    struct spsc_queue_item {
        uint8_t ID;
        data_t  data;
    };

    // process control
    boost::atomic_bool shutdown_flag { false };

    namespace statistics {
        namespace events {
            boost::atomic_size_t occurred  { 0 };
        }

        namespace packets {
            boost::atomic_size_t queued    { 0 };
            boost::atomic_size_t dropped   { 0 };
            boost::atomic_size_t processed { 0 };
            boost::atomic_size_t skipped[NUM_SOURCES] = {};
        }

        boost::atomic_size_t idle_cycles   { 0 };

        void report();
    }

    // business logic
    boost::atomic_bool   source_enabled  [NUM_SOURCES] = {}; // true:started (process) / false:stopped (skip)
    boost::lockfree::spsc_queue<spsc_queue_item, boost::lockfree::capacity<2048> > shared_queue;
}

正如您所看到的,我更改了数据(因为没有void*更容易演示)。此外,在rest运行结束时,我还添加了许多有用的统计数据,这些统计数据可以是report()-ed。

代码语言:javascript
运行
AI代码解释
复制
void producer_thread() {
    using namespace boost;
    namespace stats = queueing::statistics;
    // helpers to generate random data packets or start/stop filter events

    enum kind_t { knd_data, knd_start, knd_stop };
    queueing::data_t const empty {};
    struct event_t { kind_t kind; spsc_queue_item item; };
    // ...

    // now generate queue items in a loop
    while (!queueing::shutdown_flag) {
        auto evt = gen_event();

        std::this_thread::sleep_for(std::chrono::nanoseconds(engine()%102400));

        switch(evt.kind) {
            case knd_data:
                stats::events::occurred++;

                if (queueing::shared_queue.push(evt.item)) {
                    stats::packets::queued++;
                } else {
                    stats::packets::dropped++;
                }
                break;
            case knd_start: {
                    bool expected = false;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, true))
                        std::cout << "+";// << static_cast<int>(evt.item.ID);
                }
                break;
            case knd_stop: {
                    bool expected = true;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, false))
                        std::cout << "-";// << static_cast<int>(evt.item.ID);
                }
                break;
        }
    }
}

线程函数的主体是非常直接的,但值得注意的是,startstop事件没有通过队列进行通信。

制片人甚至更简单。它所做的就是耗尽队列,更新一些统计计数器。在处理项之前,将检查相应的筛选状态(source_enabled):

代码语言:javascript
运行
AI代码解释
复制
void consumer_thread() {
    namespace stats = queueing::statistics;

    queueing::spsc_queue_item item;

    auto consume_pending = [&] {
        while (queueing::shared_queue.pop(item)) {
            if (queueing::source_enabled[item.ID])
                fake_process(item); // if filtering started, process 
            else
                stats::packets::skipped[item.ID]++; // if filtering stopped, skip 
        }
    };

    while (!queueing::shutdown_flag) {
        consume_pending();
        stats::idle_cycles++;
    }

    consume_pending(); // drain any remaining queued items, to avoid race with shutdown_flag
}

现在,一切都应该是不言自明的,所以,拼凑一个main()函数:

代码语言:javascript
运行
AI代码解释
复制
int main() {
    using namespace std;

    // check no source_enabled flags are set at start
    assert(0 == count(begin(queueing::source_enabled), end(queueing::source_enabled), true));

    auto producer = thread(producer_thread);
    auto consumer = thread(consumer_thread);

    this_thread::sleep_for(chrono::seconds(1));
    queueing::shutdown_flag = true;

    if (producer.joinable()) producer.join();
    if (consumer.joinable()) consumer.join();

    queueing::statistics::report();
}

我们的程序运行这两个线程大约1秒,并等待他们加入。然后,它报告统计数据,在我的系统上,看起来像:

代码语言:javascript
运行
AI代码解释
复制
++-+++++++--+++-++++-++-+++---+-+-+-+++++-+--+---+++-++---+-++-++-+-+++---++--+++-++---+----+-+-+-+--+++-++--+--+--++--+-+-+-+--+--+++--++-+-++-++-+--+--+++-++-+---+----++-+++-+-++-+----+--+-+-+--+++--+++++-+-+--++-+--++++-+-+---++-+---+-+--++---++++----+-+---+-+-+-+--+-++--+-+++--+++-+----+-+-+-+++-+++--+-++-++++++---++--+-++-++---+-+-++--+-+-----++---+-+-+--+++--++---++--+-+++-++++-+++-+-+--+++-+-+----+-++++-+--+++----+++-------+-++-+-+-++++-++++---++-+---+-++-----+-++++----+++-++++--+--+-----+-++++----++++-+++-+---+---+-+-++++-++---+-++-+-+-+++-+-+--+-----++-+++---+-++---+++-++-+--+++++------++---+-++++-+-+-+--++++-++++-+--+++-++---+-----++-+-++-+-+++--++-+-+-++-++-----+-++--+--+--+-------++--+-++-+--++-++-++--+-+-++-+-+++-++++-+---+--+++--++--+-+++++-+-----++--++--+++--++-+---++----+--+-+--++-++---+++++++-+--+-++---+----+-+-+--+-+-+--++++-++--+--+-+---+++-+++++++-++-+-----+--++------+-++++++--++-++-+---+-++---++-++------+-++--+-++-+++--+++-+++-+-+--+-+--+--+---+-+-+-+--+-++-+-++---+++-+-+-++--+-++-+---++--+-+--++-+++-+--+++---+----+--++-++++++-++-+----+++-+-+--+++-----+---+--++-+--+-++++++-+-+++--+++---+-+-++++-++-+-+----++++----+++-++----+---++-+---++-+-+-++--+++---+--+++----++-++-+++--+--+---+++--+--+--+--+--++++-++++---+-+-+--+-+-+--++++--+-+--++--++++----++-++++++-+--+-+------+-----+++----++-+++++-+--+--+---++-+-++-+--++++-+++---+++-+----+--+++++-+-+--+++--++-+++-+-++---++-++-+-+-+--+-++--+---+-+++--+++++-----+-++-+-+++-+-+-------++++---+-+-++-+--+++++---+--++-+-++-+++----+++-++++---++------+-+---++++--+-+---+++------++++++---++-+----+-+++-+--++-+-+-+-----+-++-++-++--++-+-+-++++++--++---+-+-+-+-+-+-++-++-++----++--++-+++-++---+++--+++---+++--+-+++----++--+-+-+++---++---++-+--+++++-+---++----++--+++-+--+-+++++++-+--+---+--+---+----+-++-++-+--++--+--++-++---+++++--+-+---+-++-+-+----+++-++-+-+--+---+-++-+-----++---++++--+++++-+---+-++--+-+-+----+--++++-+-----++++--++-+-+++++----+++---+++++++--+---+--+--++++--+++-----+-++--+-+-----+++++----+-++++---+-++--+-++-+++--+++-+-+++++--+----++--+--+-+-++-----++-+--++--++++++-+-+++----++++---++-+--+-+------+-+--+++++--+++--++-----+--++-++-+++++-++-------+----++-++--+--++--++++-++---+-+++++----+-++-++---+++---+-++-++----++--++--+++++-+--+-----+-+-+-+-+++-+--++-+-+++--+-+-+++-+-++--+-+-+-+--+-+-+++++---+---+-++-+---++-+-++-+-+++-++-++-+-++-------++---+-++-++++-++--++--+-++-+++---++++--+----+---+-++-+++--+-+++---+-++-++----+--+--+-++--+-++-++++++--+-++-+--+---+-+--+-+--++---+--+-++--++--+--++-++++----+--+--+++-+++-+-+-++--++-+-+---+-+-------+--++++++-++++++-++-+-++-+---+--+-+-++--+++---+----+--+--+-++----+-+-++-++-++-+++--++---++-------+++++--+-+++++++--+--+-+--++--++--++-+--+--+++----+++++-++-------++---+-+--++-++--+++-+-+-+-+------+-+--+++++-+-+--++-++-++--+++++++---+-++--+++-+++--++++-++--+-+---+----+----+---+--+-+++-+-+++++---+--++--+-+++-+++++--+---+-+++++-+---++++--+-++----+---++----+++---+++++-+-++--+--+-++-++----+---++-++-+-+-+---+++-++-+++-+---+++--+-+-----++-+---++-+---++---+-++--++++-+--++-+-++----+-+-+--++--++++--+--++--+--+-+-+++++++--++-+-+-+++--+---+++--++++++--+-+-----+---++-+++--+++--++---+++--+--+-++++-----+++-----++++--++--+-+--
Events occurred:   3061
Queued packets:    3061
Dropped packets:   0
Processed packets: 1464
Filtered (per source) 58 48 53 51 47 39 45 42 53 52 57 50 63 43 49 57 45 58 40 42 56 54 58 52 44 53 61 41 50 33 51 52 
Total filtered:   1597
Idle cycles:      26408166

第一行(++-+++++++--+++-++++-++-+++---+.)是一个简写符号,表示source_enabled[]标志中有效更改的数量。

您可以看到,在这种情况下,队列没有饱和,并且使用者线程有相当多的空闲周期。

演示住在Coliru

完整的清单供参考:

代码语言:javascript
运行
AI代码解释
复制
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/atomic.hpp>
#include <boost/random.hpp>
#include <boost/bind.hpp>
#include <thread>
static constexpr uint8_t NUM_SOURCES = 32;

namespace queueing {
    using data_t = std::vector<char>; // just for demo

    struct spsc_queue_item {
        uint8_t ID;
        data_t  data;
    };

    // process control
    boost::atomic_bool shutdown_flag { false };

    namespace statistics {
        namespace events {
            boost::atomic_size_t occurred  { 0 };
        }

        namespace packets {
            boost::atomic_size_t queued    { 0 };
            boost::atomic_size_t dropped   { 0 };
            boost::atomic_size_t processed { 0 };
            boost::atomic_size_t skipped[NUM_SOURCES] = {};
        }

        boost::atomic_size_t idle_cycles   { 0 };

        void report() {
            namespace stats = queueing::statistics;
            std::cout << "\n";
            std::cout << "Events occurred:   " << stats::events::occurred   << "\n";
            std::cout << "Queued packets:    " << stats::packets::queued    << "\n";
            std::cout << "Dropped packets:   " << stats::packets::dropped   << "\n";
            std::cout << "Processed packets: " << stats::packets::processed << "\n";

            std::cout << "Filtered (per source) ";
            std::copy(std::begin(stats::packets::skipped), std::end(stats::packets::skipped), 
                    std::ostream_iterator<size_t>(std::cout, " "));
            std::cout << "\n";

            auto total_filtered = std::accumulate(std::begin(stats::packets::skipped), std::end(stats::packets::skipped), 0ul);
            std::cout << "Total filtered:   " << total_filtered << "\n";
            std::cout << "Idle cycles:      " << stats::idle_cycles      << "\n";
        }
    }

    // business logic
    boost::atomic_bool   source_enabled  [NUM_SOURCES] = {}; // true:started (process) / false:stopped (skip)
    boost::lockfree::spsc_queue<spsc_queue_item, boost::lockfree::capacity<2048> > shared_queue;
}

void producer_thread() {
    using namespace boost;
    namespace stats = queueing::statistics;
    // generate random data packets or start/stop filter events

    using queueing::spsc_queue_item;

    mt19937 engine;
    auto gen_srce = bind(uniform_int<uint8_t>(0, NUM_SOURCES-1), ref(engine));
    auto gen_data = [&] {
        std::vector<char> v; 
        std::generate_n(back_inserter(v), engine()%1024, bind(uniform_int<uint8_t>{}, ref(engine)));
        return v;
    };
    enum kind_t { knd_data, knd_start, knd_stop };
    auto gen_kind = bind(uniform_int<uint8_t>(knd_data, knd_stop), ref(engine));

    queueing::data_t const empty {};

    // 
    struct event_t { kind_t kind; spsc_queue_item item; };
    auto gen_event = [&] {
        auto kind = static_cast<kind_t>(gen_kind());
        return event_t {
            kind,
            spsc_queue_item {
                gen_srce(),
                kind == knd_data? gen_data() : empty
            }
        };
    };

    // now that we can easily generate queue items, let's do so in a loop
    while (!queueing::shutdown_flag) {
        auto evt = gen_event();

        std::this_thread::sleep_for(std::chrono::nanoseconds(engine()%102400));

        switch(evt.kind) {
            case knd_data:
                stats::events::occurred++;

                if (queueing::shared_queue.push(evt.item)) {
                    stats::packets::queued++;
                } else {
                    stats::packets::dropped++;
                }
                break;
            case knd_start:
                {
                    bool expected = false;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, true))
                        std::cout << "+";// << static_cast<int>(evt.item.ID);
                }
                break;
            case knd_stop:
                {
                    bool expected = true;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, false))
                        std::cout << "-";// << static_cast<int>(evt.item.ID);
                }
                break;
        }
    }
}

void fake_process(queueing::spsc_queue_item const& item) {
    // pretend it takes time proportional to the amount of data
    std::this_thread::sleep_for(std::chrono::microseconds(item.data.size()));

    queueing::statistics::packets::processed++;
}

void consumer_thread() {
    namespace stats = queueing::statistics;

    queueing::spsc_queue_item item;

    auto consume_pending = [&] {
        while (queueing::shared_queue.pop(item)) {
            if (queueing::source_enabled[item.ID])
                fake_process(item); // if filtering started, process 
            else
                stats::packets::skipped[item.ID]++; // if filtering stopped, skip 
        }
    };

    while (!queueing::shutdown_flag) {
        consume_pending();
        stats::idle_cycles++;
    }

    consume_pending(); // drain any remaining queued items, to avoid race with shutdown_flag
}

#include <cassert>

int main() {
    using namespace std;

    // check no source_enabled flags are set at start
    assert(0 == count(begin(queueing::source_enabled), end(queueing::source_enabled), true));

    auto producer = thread(producer_thread);
    auto consumer = thread(consumer_thread);

    this_thread::sleep_for(chrono::seconds(1));
    queueing::shutdown_flag = true;

    if (producer.joinable()) producer.join();
    if (consumer.joinable()) consumer.join();

    queueing::statistics::report();
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34766287

复制
相关文章
Google JS API 授权 失败
// 初始化OAuth2.0授权 const authenticate = () => { return gapi.auth2.getAuthInstance() .signIn({scope: "https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/driv
拿我格子衫来
2022/01/24
4.1K0
Google JS API 授权 失败
使用Google JS api 创建 文档
https://developers.google.com/docs/api/reference/rest/v1/documents/request#Request
拿我格子衫来
2022/01/24
3.3K0
使用Google JS api 创建 文档
Google短网址的API
除了速度快,goo.gl还提供详细的点击统计。比如,Yahoo首页的短网址是http://goo.gl/QuXj,那么它的统计数据就在http://goo.gl/info/QuXj。加上后缀".qr",还能得到这个网址的二维条形码,Yahoo的就是http://goo.gl/QuXj.qr。
ruanyf
2018/09/21
4.4K1
Google短网址的API
Google JavaScript API
You can use the JavaScript client library to interact with Google APIs, such as People, Calendar, and Drive, from your web applications. Follow the instructions on this page to get started.
拿我格子衫来
2022/01/24
6140
Google 发布 Google Friend Connect API
Google Friend Connect 是 Google 推出的社会化网络工具,通过此工具你可以将各种支持 OpenSocial 的应用通过 Google Friend Connect 在你的网站上应用,并且可以和已有的社会化网络进行整合应用。今天 Google 更是开放了 Google Friend Connect 的 API,让你能够访问到更多 Google Friend Connect 核心的数据和功能。 Google Friend Connect 提供两种 API,JavaScript API 允许你能够直接集成社会化社区到你的网页中。REST API 能够允许你把网站的现有的登陆系统和数据集成新的社会化数据和活动,并能实现让你的网站实现通过 Gmail 账号,Yahoo 账号,OpenID 等方式实现单点登录。
Denis
2023/04/14
6630
Google JavaScript API 的使用
您可以使用JavaScript客户端库与Web应用程序中的Google API(例如,人物,日历和云端硬盘)进行交互。请按照此页面上的说明进行操作。
拿我格子衫来
2022/01/24
3K0
google maps api_js调用谷歌浏览器接口
1. 使用谷歌地图 API 的第一步就是要注册一个 API 密钥,需要注重一下两点:
全栈程序员站长
2022/09/20
5.8K0
JavaScript---网络编程(7)-Dom模型(节点间的层次关系,节点的增、删、改)
利用节点间的层次关系获取节点: 上一节讲了3中获取的方式: * ※※一、绝对获取,获取元素的3种方式:—Element * 1、getElementById(): 通过标签中的id属性值获来取该标签对象 * 2、getElementsByName(): 通过标签中的name属性值来获取该标签对象集合 * 3、getElementsByTagName(): 通过标签名来获取该标签对象集合
谙忆
2021/01/21
8560
JavaScript---网络编程(7)-Dom模型(节点间的层次关系,节点的增、删、改)
js|jq获取兄弟节点,父节点,子节点
08.19自我总结 js|jq获取兄弟节点,父节点,子节点 一.js var parent = test.parentNode; // 父节点 var chils = test.childNodes; // 全部子节点 var first = test.firstChild; // 第一个子节点 var last = test.lastChile; // 最后一个子节点  var previous = test.previousSibling; // 上一个兄弟节点 var next = test.next
小小咸鱼YwY
2019/09/11
15.2K0
折腾Google Docs API 的坑
快速开始 https://developers.google.cn/docs/api/quickstart/nodejs#step_2_install_the_client_library
拿我格子衫来
2022/01/24
1.3K0
折腾Google Docs API 的坑
使用Google翻译Api
将环境变量GOOGLE_APPLICATION_CREDENTIALS设置为包含服务帐户密钥的JSON文件的文件路径。在Linux或macOS系统中设置方法如下:
职场亮哥
2020/10/10
4.6K0
Facebook Ads广告业务API接口的源代码泄露漏洞
此前,我对“Windows NT” 和 “Windows Phone”模型有所研究,后来,我看到好多人参与了Facebook的漏洞赏金项目并收获了奖励,所以,我想那我也来试试吧,看看能不能入围Facebook的白帽致谢榜,想当年我也两次入围微软操作系统漏洞安全名人堂呢。
FB客服
2018/12/28
1.2K0
怎么解决google ads广告被拒登 存在恶意软件或垃圾软件的问题
2020年google adwords上线了最新的安全算法,针对客户网站存在恶意软件以及垃圾软件的情况,将会直接拒绝推广,显示已拒登:恶意软件或垃圾软件的提示。导致国内大部分做外贸以及google推广的客户受到影响,很多客户找到我们SINE安全公司寻求技术上的支持,帮忙解决问题,促使goole广告尽快上线。像这种问题该如何解决处理呢?
网站安全专家
2020/04/24
1.3K0
怎么解决google ads广告被拒登 存在恶意软件或垃圾软件的问题
ADS1115IDGSR
生产厂家:TEXAS INSTRUMENTS 型号参数:ADS1115IDGSR参数Brand NameTexas Instruments是否无铅不含铅是否Rohs认证符合生命周期ActiveIHS 制造商TEXAS INSTRUMENTS INC零件包装代码MSOP包装说明MSOP-10针数10Reach Compliance CodecompliantECCN代码EAR99HTS代码8542.39.00.01Factory Lead Time1 week风险等级1.21Samacsys Confide
电子交流圈
2022/03/20
6030
ADS振铃仿真
T=35um,表面导体厚度,1oz铜为35um,0.5oz铜为17um,此处设置为1oz;
黑马Amos
2023/03/21
1.1K0
ADS振铃仿真
js控制节点小结
DOM节点操作 <style> *{ margin: 0; padding: 0; } ul{ list-style: none; } a{ text-decoration: none; color: #333; } div{ margin-left
天天_哥
2018/09/29
5.9K0
js创建img节点
我们需要使用document对象的createElement方法创建了一个img元素:
IT工作者
2022/01/05
8.2K0
ADS1.2破解
ads1.2 license 1.拷贝{}内容到文本文档里面( 不包括{} ) 2.改成 .dat 3.按着向导导入即可 {PACKAGE ads armlmd 1.200 E32F0DE5161D COMPONENTS="armasm compiler \ bats armulate axd adwu fromelf armlink codewarrior armsd" INCREMENT ads armlmd 1.200 permanent uncounted 612C53EF47C7 \ HOSTID=ANY ISSUER="Full License by armer, only for educational purpose!" ck=0 }
TSINGEYE清眸物联
2023/01/04
6860
Js 类型转换
JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:
hss
2022/02/25
20.4K0
js时间转换
//时间戳格式化 //时间转换 function stamptime(time) { var date = new Date(time) var Y = date.g
阿超
2022/08/16
12.2K0

相似问题

Google Ads API -许可

11

Google Ads API集成

29

Google Ads API帐户丢失

10

更新预算Google Ads API

10

将Google-Ads API GoogleAdsRow转换为CSV?

114
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文