窃取任务队列 steal thread pool

本文介绍了一种使用互斥锁保护的同步双端队列(sync_deque)模板类,以及基于该数据结构实现的worker_t工作线程类和thread_pool线程池类。这些类支持任务的线程安全分配和执行,包括从队列中分配任务、检查队列是否为空、获取队列大小等操作。
#include <time.h>
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <functional>
#include <algorithm>
#include <deque>
#include <memory>
#include <chrono>
#include <atomic>
template <class T>
class sync_deque final {
public:
    sync_deque() = default;
	~sync_deque() = default;
	void push_front(const T &val) {
		std::unique_lock<std::mutex>lk(lock_);
		deque_.push_front(val);
	}
    T pop_front() {
        std::unique_lock<std::mutex>lk(lock_);
		if (true == deque_.empty()) {
			return nullptr;
		}
		T val = deque_.front();
		deque_.pop_front();
		return val;
	}
	T pop_back() {
        std::unique_lock<std::mutex>lk(lock_);
		if (true == deque_.empty()) {
			return nullptr;
		}
		T val = deque_.back();
		deque_.pop_back();
		return val;
	}
	inline bool empty() {
		return true == deque_.empty();
	}
    inline size_t get_size() {
        std::unique_lock<std::mutex>l(lock_);
		return deque_.size();
    }
private:
	std::deque<T>deque_;
	std::mutex lock_;
};
class worker_t;
using workers_ptr = std::shared_ptr<std::vector<std::shared_ptr<worker_t>>>;
using task_t = std::function<void()>;
class worker_t final {
public:
    explicit worker_t(workers_ptr workers, size_t work_num) : workers_(workers) {
        work_num_ = work_num;
        enabled_ = true;
        thd_ = std::thread(std::bind(&worker_t::execute, this));
    }
    inline void assign(const task_t &task) {
        queue_.push_front(task);
    }
    inline bool empty() {
        return true == queue_.empty();
    }
    inline task_t steal() {
        return queue_.pop_back();
    }
    void join() {
        enabled_ = false;
        if (thd_.joinable()) {
            std::cout << "thread join." << std::endl;
            thd_.join();
        }
    }
private:
    void execute_stealed_task() {
        int rand_select = rand() % work_num_;
        auto worker = workers_->at(rand_select);
        std::cout << rand_select << " work thread will be selected." << std::endl;
        auto task = worker->steal();
        if (nullptr != task) {
            task();
        }
    }
    void execute() {
        thread_id_ = std::this_thread::get_id();
        std::cout << "thread id = " << thread_id_ << std::endl;
        while (enabled_) {
            if (work_num_ != workers_->size()) {
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                continue;
            }
            task_t task = queue_.pop_front();
			while (task != nullptr) {
				task();
				task = queue_.pop_front();
			}
            bool no_task = std::all_of(workers_->begin(), workers_->end(), [] (std::shared_ptr<worker_t> worker) { return worker->empty(); });
            if (true == no_task) {
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                continue;
            }
            execute_stealed_task();
        }
    }
private:
    size_t work_num_;
    bool enabled_;
    std::thread thd_;
    workers_ptr workers_;
    std::thread::id thread_id_;
    sync_deque<task_t>queue_;
};
class thread_pool {
public:
    thread_pool(size_t thread_num = std::thread::hardware_concurrency()) {
        std::srand(time(nullptr));
        workers_ = std::make_shared<std::vector<std::shared_ptr<worker_t>>>();
        for (int i = 0;i < thread_num;i++) {
            auto worker = std::make_shared<worker_t>(workers_, thread_num);
            workers_->emplace_back(worker);
        }
    }
    ~thread_pool() {
        for (auto worker : *workers_) {
            worker->join();
        }
        workers_->clear();
    }
    inline void add_task(const task_t &task) {
        auto worker = get_rand_worker();
        worker->assign(task);
    }
private:
    inline std::shared_ptr<worker_t> get_rand_worker() {
        static size_t size = workers_->size();
        return workers_->at(rand() % size);
    }
private:
    workers_ptr workers_;
};
std::atomic<int>global_num;
int main() {
    thread_pool pool;
    for(size_t i = 0; i < 100; i++) {
        pool.add_task([] {
            ++global_num;
            std::cout << "global num = " << global_num.load() << std::endl;            
        });
    }

    return 0;
}

 

<think>我们正在讨论Linux多线程编程中如何处理阻塞任务。根据提供的引用资料,我们知道线程是进程内的执行单元,共享进程资源,多线程可以减少系统资源消耗并提高效率。然而,在多线程中处理阻塞任务(如I/O操作)时,需要特别注意,因为一个线程的阻塞可能会影响整个程序的性能。处理阻塞任务的方法:1.使用多线程:为每个阻塞任务创建单独的线程,这样当一个线程阻塞时,其他线程可以继续执行。但要注意线程数量不宜过多,否则会导致上下文切换开销增大。2.非阻塞I/O:使用非阻塞I/O操作(如设置文件描述符为非阻塞模式),配合I/O多路复用(如select,poll,epoll)技术,可以在一个线程中同时监控多个I/O事件,避免为每个I/O操作都创建一个线程。3.异步I/O:使用异步I/O(如Linux的aio)操作,当I/O操作完成时,系统会通知线程,这样线程在发起I/O请求后就可以继续执行其他任务,而不需要阻塞等待。4.线程池:创建固定数量的线程,将阻塞任务提交到线程池中执行。这样可以控制线程数量,避免线程过多导致的资源浪费和上下文切换开销。具体实现:根据引用[1],线程共享进程的全局变量和内存空间,因此在线程间共享数据时要注意同步问题。我们可以使用互斥锁(mutex)、条件变量(conditionvariable)等机制来同步线程。示例代码(使用线程池处理阻塞任务):下面是一个简单的线程池实现,用于处理阻塞任务。我们使用POSIX线程库(pthread)来实现。步骤:1.创建任务队列,用于存放待处理的阻塞任务。2.创建线程池,包含固定数量的工作线程。3.工作线程从任务队列中取出任务并执行(如果队列为空,则等待)。4.主线程将阻塞任务添加到任务队列。注意:需要互斥锁保护任务队列,条件变量用于通知工作线程有新任务到来。代码示例:```c#include <pthread.h> #include<stdio.h>#include <stdlib.h> //任务结构体typedefstruct {void(*function)(void*);void*arg; }Task; //线程池结构体typedef struct{pthread_mutex_tlock;//互斥锁pthread_cond_tcond;//条件变量Task*task_queue;//任务队列intqueue_size;//队列大小int head;//队列头int tail;//队列尾int count;//当前任务数int shutdown;//关闭标志pthread_t*threads;//线程数组intthread_count;//线程数量}ThreadPool; //工作线程函数void*worker(void *arg){ThreadPool*pool= (ThreadPool*)arg;while (1){pthread_mutex_lock(&pool->lock);//等待任务队列非空while(pool->count==0 &&!pool->shutdown){pthread_cond_wait(&pool->cond,&pool->lock);}if(pool->shutdown){pthread_mutex_unlock(&pool->lock);pthread_exit(NULL);}//取出任务Tasktask =pool->task_queue[pool->head];pool->head =(pool->head+1)% pool->queue_size;pool->count--;pthread_mutex_unlock(&pool->lock);//执行任务(*(task.function))(task.arg);}return NULL; }//初始化线程池ThreadPool*thread_pool_create(int thread_count,int queue_size){ThreadPool*pool= (ThreadPool*)malloc(sizeof(ThreadPool));pool->thread_count= thread_count;pool->queue_size =queue_size;pool->head =0;pool->tail =0;pool->count =0;pool->shutdown=0;pool->task_queue =(Task*)malloc(sizeof(Task)* queue_size);pool->threads =(pthread_t*)malloc(sizeof(pthread_t) *thread_count);pthread_mutex_init(&pool->lock,NULL);pthread_cond_init(&pool->cond, NULL);for (int i=0;i <thread_count; i++) {pthread_create(&pool->threads[i],NULL,worker,pool);}returnpool;} //添加任务void thread_pool_add_task(ThreadPool *pool,void (*function)(void *), void*arg) {pthread_mutex_lock(&pool->lock);if(pool->count== pool->queue_size){//队列已满,根据实际情况处理(例如等待或返回错误)pthread_mutex_unlock(&pool->lock);return;}//添加到队列尾部pool->task_queue[pool->tail].function= function;pool->task_queue[pool->tail].arg= arg;pool->tail =(pool->tail+1)% pool->queue_size;pool->count++;//通知一个工作线程pthread_cond_signal(&pool->cond);pthread_mutex_unlock(&pool->lock);} //销毁线程池voidthread_pool_destroy(ThreadPool*pool) {pthread_mutex_lock(&pool->lock);pool->shutdown =1;pthread_mutex_unlock(&pool->lock);//唤醒所有线程pthread_cond_broadcast(&pool->cond);for(inti =0; i< pool->thread_count;i++){pthread_join(pool->threads[i],NULL);}free(pool->threads);free(pool->task_queue);pthread_mutex_destroy(&pool->lock);pthread_cond_destroy(&pool->cond);free(pool); }//示例任务函数void example_task(void*arg) {int*num =(int*)arg;printf("Task isrunningwithargument:%d\n",*num);//模拟阻塞操作,比如I/Osleep(1);} intmain(){ThreadPool*pool= thread_pool_create(4,10); //4个线程,队列大小为10for (int i=0;i <20; i++) {int*arg= (int *)malloc(sizeof(int));*arg =i;thread_pool_add_task(pool, example_task,arg);}//等待所有任务完成(这里简单等待,实际中可能需要更复杂的机制)sleep(5);thread_pool_destroy(pool);return0;} ```上述代码创建了一个简单的线程池,可以处理阻塞任务(在示例任务中模拟了阻塞操作)。注意,在实际使用中,需要根据任务特性调整线程数量和队列大小。总结:处理多线程阻塞任务时,使用线程池是一种常见且有效的方法,它可以避免频繁创建和销毁线程的开销,并控制并发线程数量。同时,结合非阻塞I/O或异步I/O技术可以进一步提高程序的性能。引用说明:根据引用[1],线程共享进程资源,因此线程池中的线程可以共享任务队列(需要同步机制)。引用[3]提到,在I/O密集型应用中,多线程可以提高性能,但要注意线程数量。线程池正是控制线程数量的有效方法。注意:在编写多线程程序时,务必注意线程安全,使用互斥锁等机制保护共享资源,避免竞态条件。 §§相关问题§§1.线程池中任务队列已满时如何处理新任务?2.除了线程池,还有哪些处理多线程阻塞任务的方法?3.如何避免多线程编程中的死锁问题?</think>### Linux多线程编程中阻塞任务的处理方法 在Linux环境下处理多线程阻塞任务,核心是通过合理的线程管理和同步机制确保程序高效运行。以下是关键处理方法: #### 1. **线程池技术** 使用固定数量的线程处理任务队列,避免频繁创建/销毁线程的开销: ```c // 创建线程池(伪代码) pthread_t thread_pool[POOL_SIZE]; for(int i=0; i<POOL_SIZE; i++){ pthread_create(&thread_pool[i], NULL, worker, NULL); } // 工作线程函数 void* worker(void* arg){ while(1){ Task task = get_task_from_queue(); // 从任务队列任务 process_blocking_task(task); // 执行阻塞任务 } } ``` - **优势**:控制线程数量,避免资源耗尽 - **适用场景**:高并发I/O操作(如网络服务)[^3] #### 2. **非阻塞I/O + I/O多路复用** 结合`epoll`/`select`处理多个I/O事件: ```c int epoll_fd = epoll_create1(0); while(1){ int ready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for(int i=0; i<ready; i++){ if(events[i].events & EPOLLIN){ handle_nonblocking_io(events[i].data.fd); // 非阻塞处理 } } } ``` - **优势**:单线程管理多个I/O描述符,避免线程阻塞 - **典型应用**:高并发服务器[^3] #### 3. **条件变量同步** 使用`pthread_cond_t`唤醒阻塞线程: ```c pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 等待条件 pthread_mutex_lock(&mutex); while(!condition){ pthread_cond_wait(&cond, &mutex); // 阻塞等待唤醒 } pthread_mutex_unlock(&mutex); // 唤醒线程 pthread_cond_signal(&cond); ``` - **关键作用**:精确控制线程阻塞/唤醒时机[^1] #### 4. **异步I/O(AIO)** 使用Linux AIO接口: ```c struct aiocb cb = { .aio_fildes = fd, .aio_buf = buffer, .aio_nbytes = size }; aio_read(&cb); // 发起异步读 while(aio_error(&cb) == EINPROGRESS); // 非阻塞检查 ``` - **优势**:I/O操作在后台执行,主线程不阻塞 - **适用场景**:磁盘文件操作[^2] #### 5. **任务队列+工作窃取** 实现负载均衡: ```c // 每个线程有自己的任务队列 while(1){ if(!local_queue_empty()){ task = pop_local_queue(); } else { task = steal_from_other_queue(); // 窃取其他线程任务 } execute_task(task); } ``` - **优势**:避免线程空闲,提升CPU利用率[^4] ### 最佳实践建议 1. **线程数量控制**: - I/O密集型:线程数 ≈ 2 * CPU核心数 - CPU密集型:线程数 ≈ CPU核心数[^3] 2. **避免全局锁竞争**: - 使用细粒度锁或无锁数据结构 3. **超时机制**: - 给阻塞操作设置超时时间(如`pthread_cond_timedwait`) 4. **优先级继承**: - 防止优先级反转(`pthread_mutexattr_setprotocol`) > 多线程编程的核心是在阻塞操作不可避免时,通过架构设计将阻塞影响局部化,同时确保系统整体保持响应性。选择方案需结合具体场景:高并发网络服务优先考虑I/O多路复用,计算密集型任务适合线程池,文件操作推荐异步I/O[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值