场景
1.C++标准库没有提供线程池操作, 连Win32都没有集成线程池, 相比之下macOS完善多了, 至少有operations. 多线程在执行多任务时有很大优势, 比如同时管理多个设备, 多个socket连接等.
2.第3方库也有很多, 最常见的是boost的线程, 可是boost编译的成本也很高, 打包也很大, 一般不考虑.
说明
1.Programming With POSIX Threads 的 7.2章提供的 Work queue manager是个不错的选择, 按需创建线程还是不错的. 还有线程的timeout时间,线程池销毁, 不怕占用资源. 当然线程安全方式的添加任务void* 不用说. 稍微改造下成为C++的ThreadPool还是很容易的, 比我实现的 线程池的简单设计与实现 要好, 有时间会改造下.
2.以下简单说明下函数的作用:
workq_add: 添加任务到队列, 根据需要提供自己的自定义类型, 这里它提供了统一类型. power_t, 一般使用时可以自定义一个包裹类型,{data,func},这样在调用 engine_routine 里可以通过 func(data)来转发.
workq_init: 初始化一个队列, 它这里提供了一个统一的队列的处理函数 engine_routine, 注意这个处理函数对这个队列是唯一的.
workq_server: 这个是worker线程, 不要被这个名字骗了, 它对应一个线程, 可以是并发的.
destructor: 这个只是为了统计执行这个workq 所需要的 worker线程个数.
engine_routine: 这个是业务逻辑处理函数, 在workq_server里调用.
thread_routine: 这里是创建任务的函数,例子里两个线程分别调用了1次,即有50次任务创建.
3.这里我改写了linux代码为Windows项目, 没改多少内容, 因为pthreads-win32可以在Windows里使用, 只是获取 pthread_cond_timedwait 的参数 struct timespec 比较麻烦. 我这里的 millisecondsFromNow 还是通过参考pthread-win32的自带例子的实现.
代码
workq.c
/*
* workq.c
*
* This file implements the interfaces for a "work queue"
* manager. A "manager object" is created with several
* parameters, including the required size of a work queue
* entry, the maximum desired degree of parallelism (number of
* threads to service the queue), and the address of an
* execution engine routine.
*
* The application requests a work queue entry from the manager,
* fills in the application-specific fields, and returns it to
* the queue manager for processing. The manager will create a
* new thread to service the queue if all current threads are
* busy and the maximum level of parallelism has not yet been
* reached.
*
* The manager will dequeue items and present them to the
* processing engine until the queue is empty; at that point,
* processing threads will begin to shut down. (They will be
* restarted when work appears.)
*/
#include <sys/types.h>
#include <sys/timeb.h>
#include <Windows.h>
#include <time.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include "errors.h"
#include "workq.h"
/*
* Returns abstime 'milliseconds' from 'now'.
*
* Works for: -INT_MAX <= millisecs <= INT_MAX
*/
#if defined(_MSC_VER) && _MSC_VER >= 1400
# define PTW32_FTIME(x) _ftime64_s(x)
# define PTW32_STRUCT_TIMEB struct __timeb64
#elif ( defined(_MSC_VER) && _MSC_VER >= 1300 ) || \
( defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x0601 )
# define PTW32_FTIME(x) _ftime64(x)
# define PTW32_STRUCT_TIMEB struct __timeb64
#else
# define PTW32_FTIME(x) _ftime(x)
# define PTW32_STRUCT_TIMEB struct _timeb
#endif
struct timespec *
millisecondsFromNow (struct timespec * time, int millisecs)
{
PTW32_STRUCT_TIMEB currSysTime;
int64_t nanosecs, secs;
const int64_t NANOSEC_PER_MILLISEC = 1000000;
const int64_t NANOSEC_PER_SEC = 1000000000;
/* get current system time and add millisecs */
PTW32_FTIME(&currSysTime);
secs = (int64_t)(currSysTime.time) + (millisecs / 1000);
nanosecs = ((int64_t) (millisecs%1000 + currSysTime.millitm)) * NANOSEC_PER_MILLISEC;
if (nanosecs >= NANOSEC_PER_SEC)
{
secs++;
nanosecs -= NANOSEC_PER_SEC;
}
else if (nanosecs < 0)
{
secs--;
nanosecs += NANOSEC_PER_SEC;
}
time->tv_nsec = (long)nanosecs;
time->tv_sec = (long)secs;
return time;
}
/*
* Thread start routine to serve the work queue.
*/
static void *workq_server (void *arg)
{
struct timespec timeout;
workq_t *wq = (workq_t *)arg;
workq_ele_t *we;
int status, timedout;
/*
* We don't need to validate the workq_t here... we don't
* create server threads until requests are queued (the
* queue has been initialized by then!) and we wait for all
* server threads to terminate before destroying a work
* queue.
*/
DPRINTF (("A worker is starting\n"));
status = pthread_mutex_lock (&wq->mutex);
if (status != 0)
return NULL;
while (1) {
timedout = 0;
DPRINTF (("Worker waiting for work\n"));
while (wq->first == NULL && !wq->quit) {
/*
* Server threads time out after spending 2 seconds
* waiting for new work, and exit.
*/
status = pthread_cond_timedwait (
&wq->cv, &wq->mutex, millisecondsFromNow(&timeout,2000));
if (status == ETIMEDOUT) {
DPRINTF (("Worker wait timed out\n"));
timedout = 1;
break;
} else if (status != 0) {
/*
* This shouldn't happen, so the work queue
* package should fail. Because the work queue
* API is asynchronous, that would add
* complication. Because the chances of failure
* are slim, I choose to avoid that
* complication. The server thread will return,
* and allow another server thread to pick up
* the work later. Note that, if this was the
* only server thread, the queue won't be
* serviced until a new work item is
* queued. That could be fixed by creating a new
* server here.
*/
DPRINTF ((
"Worker wait failed, %d (%s)\n",
status, strerror (status)));
wq->counter--;
pthread_mutex_unlock (&wq->mutex);
return NULL;
}
}
DPRINTF (("Work queue: %#lx, quit: %d\n", wq->first, wq->quit));
we = wq->first;
if (we != NULL) {
wq->first = we->next;
if (wq->last == we)
wq->last = NULL;
status = pthread_mutex_unlock (&wq->mutex);
if (status != 0)
return NULL;
DPRINTF (("Worker calling engine\n"));
wq->engine (we->data);
free (we);
status = pthread_mutex_lock (&wq->mutex);
if (status != 0)
return NULL;
}
/*
* If there are no more work requests, and the servers
* have been asked to quit, then shut down.
*/
if (wq->first == NULL && wq->quit) {
DPRINTF (("Worker shutting down\n"));
wq->counter--;
/*
* NOTE: Just to prove that every rule has an
* exception, I'm using the "cv" condition for two
* separate predicates here. That's OK, since the
* case used here applies only once during the life
* of a work queue -- during rundown. The overhead
* is minimal and it's not worth creating a separate
* condition variable that would be waited and
* signaled exactly once!
*/
if (wq->counter == 0)
pthread_cond_broadcast (&wq->cv);
pthread_mutex_unlock (&wq->mutex);
return NULL;
}
/*
* If there's no more work, and we wait for as long as
* we're allowed, then terminate this server thread.
*/
if (wq->first == NULL && timedout) {
DPRINTF (("engine terminating due to timeout.\n"));
wq->counter--;
break;
}
}
pthread_mutex_unlock (&wq->mutex);
DPRINTF (("Worker exiting\n"));
return NULL;
}
/*
* Initialize a work queue.
*/
int workq_init (workq_t *wq, int threads, void (*engine)(void *arg))
{
int status;
status = pthread_attr_init (&wq->attr);
if (status != 0)
return status;
status = pthread_attr_setdetachstate (
&wq->attr, PTHREAD_CREATE_DETACHED);
if (status != 0) {
pthread_attr_destroy (&wq->attr);
return status;
}
status = pthread_mutex_init (&wq->mutex, NULL);
if (status != 0) {
pthread_attr_destroy (&wq->attr);
return status;
}
status = pthread_cond_init (&wq->cv, NULL);
if (status != 0) {
pthread_mutex_destroy (&wq->mutex);
pthread_attr_destroy (&wq->attr);
return status;
}
wq->quit = 0; /* not time to quit */
wq->first = wq->last = NULL; /* no queue entries */
wq->parallelism = threads; /* max servers */
wq->counter = 0; /* no server threads yet */
wq->idle = 0; /* no idle servers */
wq->engine = engine;
wq->valid = WORKQ_VALID;
return 0;
}
/*
* Destroy a work queue.
*/
int workq_destroy (workq_t *wq)
{
int status, status1, status2;
if (wq->valid != WORKQ_VALID)
return EINVAL;
status = pthread_mutex_lock (&wq->mutex);
if (status != 0)
return status;
wq->valid = 0; /* prevent any other operations */
/*
* Check whether any threads are active, and run them down:
*
* 1. set the quit flag
* 2. broadcast to wake any servers that may be asleep
* 4. wait for all threads to quit (counter goes to 0)
* Because we don't use join, we don't need to worry
* about tracking thread IDs.
*/
if (wq->counter > 0) {
wq->quit = 1;
/* if any threads are idling, wake them. */
if (wq->idle > 0) {
status = pthread_cond_broadcast (&wq->cv);
if (status != 0) {
pthread_mutex_unlock (&wq->mutex);
return status;
}
}
/*
* Just to prove that every rule has an exception, I'm
* using the "cv" condition for two separate predicates
* here. That's OK, since the case used here applies
* only once during the life of a work queue -- during
* rundown. The overhead is minimal and it's not worth
* creating a separate condition variable that would be
* waited and signalled exactly once!
*/
while (wq->counter > 0) {
status = pthread_cond_wait (&wq->cv, &wq->mutex);
if (status != 0) {
pthread_mutex_unlock (&wq->mutex);
return status;
}
}
}
status = pthread_mutex_unlock (&wq->mutex);
if (status != 0)
return status;
status = pthread_mutex_destroy (&wq->mutex);
status1 = pthread_cond_destroy (&wq->cv);
status2 = pthread_attr_destroy (&wq->attr);
return (status ? status : (status1 ? status1 : status2));
}
/*
* Add an item to a work queue.
*/
int workq_add (workq_t *wq, void *element)
{
workq_ele_t *item;
pthread_t id;
int status;
if (wq->valid != WORKQ_VALID)
return EINVAL;
/*
* Create and initialize a request structure.
*/
item = (workq_ele_t *)malloc (sizeof (workq_ele_t));
if (item == NULL)
return ENOMEM;
item->data = element;
item->next = NULL;
status = pthread_mutex_lock (&wq->mutex);
if (status != 0) {
free (item);
return status;
}
/*
* Add the request to the end of the queue, updating the
* first and last pointers.
*/
if (wq->first == NULL)
wq->first = item;
else
wq->last->next = item;
wq->last = item;
/*
* if any threads are idling, wake one.
*/
if (wq->idle > 0) {
status = pthread_cond_signal (&wq->cv);
if (status != 0) {
pthread_mutex_unlock (&wq->mutex);
return status;
}
} else if (wq->counter < wq->parallelism) {
/*
* If there were no idling threads, and we're allowed to
* create a new thread, do so.
*/
DPRINTF (("Creating new worker\n"));
status = pthread_create (
&id, &wq->attr, workq_server, (void*)wq);
if (status != 0) {
pthread_mutex_unlock (&wq->mutex);
return status;
}
wq->counter++;
}
pthread_mutex_unlock (&wq->mutex);
return 0;
}
workq.h
/*
* workq.h
*
* This header file defines the interfaces for a "work queue"
* manager. A "manager object" is created with several
* parameters, including the required size of a work queue
* entry, the maximum desired degree of parallelism (number of
* threads to service the queue), and the address of an
* execution engine routine.
*
* The application requests a work queue entry from the manager,
* fills in the application-specific fields, and returns it to
* the queue manager for processing. The manager will create a
* new thread to service the queue if all current threads are
* busy and the maximum level of parallelism has not yet been
* reached.
*
* The manager will dequeue items and present them to the
* processing engine until the queue is empty; at that point,
* processing threads will begin to shut down. (They will be
* restarted when work appears.)
*/
#include <pthread.h>
/*
* Structure to keep track of work queue requests.
*/
typedef struct workq_ele_tag {
struct workq_ele_tag *next;
void *data;
} workq_ele_t;
/*
* Structure describing a work queue.
*/
typedef struct workq_tag {
pthread_mutex_t mutex;
pthread_cond_t cv; /* wait for work */
pthread_attr_t attr; /* create detached threads */
workq_ele_t *first, *last; /* work queue */
int valid; /* set when valid */
int quit; /* set when workq should quit */
int parallelism; /* number of threads required */
int counter; /* current number of threads */
int idle; /* number of idle threads */
void (*engine)(void *arg); /* user engine */
} workq_t;
#define WORKQ_VALID 0xdec1992
/*
* Define work queue functions
*/
extern int workq_init (
workq_t *wq,
int threads, /* maximum threads */
void (*engine)(void *)); /* engine routine */
extern int workq_destroy (workq_t *wq);
extern int workq_add (workq_t *wq, void *data);
workq_main.c
/*
* workq_main.c
*
* Demonstrate a use of work queues.
*/
#include <Windows.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "workq.h"
#include "errors.h"
#define ITERATIONS 25
typedef struct power_tag {
int value;
int power;
} power_t;
typedef struct engine_tag {
struct engine_tag *link;
pthread_t thread_id;
int calls;
} engine_t;
pthread_key_t engine_key; /* Keep track of active engines */
pthread_mutex_t engine_list_mutex = PTHREAD_MUTEX_INITIALIZER;
engine_t *engine_list_head = NULL;
workq_t workq;
/*
* Thread-specific data destructor routine for engine_key
*/
void destructor (void *value_ptr)
{
engine_t *engine = (engine_t*)value_ptr;
pthread_mutex_lock (&engine_list_mutex);
engine->link = engine_list_head;
engine_list_head = engine;
pthread_mutex_unlock (&engine_list_mutex);
}
/*
* This is the routine called by the work queue servers to
* perform operations in parallel.
*/
void engine_routine (void *arg)
{
engine_t *engine;
power_t *power = (power_t*)arg;
int result, count;
int status;
engine = pthread_getspecific (engine_key);
if (engine == NULL) {
engine = (engine_t*)malloc (sizeof (engine_t));
status = pthread_setspecific (
engine_key, (void*)engine);
if (status != 0)
err_abort (status, "Set tsd");
engine->thread_id = pthread_self ();
engine->calls = 1;
} else
engine->calls++;
result = 1;
printf (
"Engine: computing %d^%d\n",
power->value, power->power);
for (count = 1; count <= power->power; count++)
result *= power->value;
free (arg);
}
/*
* Thread start routine that issues work queue requests.
*/
void *thread_routine (void *arg)
{
power_t *element;
int count;
unsigned int seed = (unsigned int)time (NULL);
int status;
srand(seed);
/*
* Loop, making requests.
*/
for (count = 0; count < ITERATIONS; count++) {
element = (power_t*)malloc (sizeof (power_t));
if (element == NULL)
errno_abort ("Allocate element");
element->value = rand() % 20;
element->power = rand() % 7;
DPRINTF ((
"Request: %d^%d\n",
element->value, element->power));
status = workq_add (&workq, (void*)element);
if (status != 0)
err_abort (status, "Add to work queue");
Sleep ((rand() % 5)*1000);
}
return NULL;
}
int main (int argc, char *argv[])
{
pthread_t thread_id;
engine_t *engine;
int count = 0, calls = 0;
int status;
status = pthread_key_create (&engine_key, destructor);
if (status != 0)
err_abort (status, "Create key");
status = workq_init (&workq, 4, engine_routine);
if (status != 0)
err_abort (status, "Init work queue");
status = pthread_create (&thread_id, NULL, thread_routine, NULL);
if (status != 0)
err_abort (status, "Create thread");
(void)thread_routine (NULL);
status = pthread_join (thread_id, NULL);
if (status != 0)
err_abort (status, "Join thread");
status = workq_destroy (&workq);
if (status != 0)
err_abort (status, "Destroy work queue");
/*
* By now, all of the engine_t structures have been placed
* on the list (by the engine thread destructors), so we
* can count and summarize them.
*/
engine = engine_list_head;
while (engine != NULL) {
count++;
calls += engine->calls;
printf ("engine %d: %d calls\n", count, engine->calls);
engine = engine->link;
}
printf ("%d engine threads processed %d calls\n",
count, calls);
system("pause");
return 0;
}
errors.h
#ifndef __errors_h
#define __errors_h
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Define a macro that can be used for diagnostic output from
* examples. When compiled -DDEBUG, it results in calling printf
* with the specified argument list. When DEBUG is not defined, it
* expands to nothing.
*/
#ifdef _DEBUG
# define DPRINTF(arg) printf arg
#else
# define DPRINTF(arg)
#endif
/*
* NOTE: the "do {" ... "} while (0);" bracketing around the macros
* allows the err_abort and errno_abort macros to be used as if they
* were function calls, even in contexts where a trailing ";" would
* generate a null statement. For example,
*
* if (status != 0)
* err_abort (status, "message");
* else
* return status;
*
* will not compile if err_abort is a macro ending with "}", because
* C does not expect a ";" to follow the "}". Because C does expect
* a ";" following the ")" in the do...while construct, err_abort and
* errno_abort can be used as if they were function calls.
*/
#define err_abort(code,text) do { \
fprintf (stderr, "%s at \"%s\":%d: %s\n", \
text, __FILE__, __LINE__, strerror (code)); \
abort (); \
} while (0)
#define errno_abort(text) do { \
fprintf (stderr, "%s at \"%s\":%d: %s\n", \
text, __FILE__, __LINE__, strerror (errno)); \
abort (); \
} while (0)
#endif
部分输出:
engine 24: 1 calls
engine 25: 1 calls
engine 26: 1 calls
engine 27: 1 calls
engine 28: 1 calls
engine 29: 1 calls
engine 30: 1 calls
engine 31: 2 calls
engine 32: 3 calls
engine 33: 2 calls
engine 34: 1 calls
engine 35: 1 calls
engine 36: 2 calls
engine 37: 1 calls
engine 38: 1 calls
38 engine threads processed 50 calls
项目下载地址: http://download.csdn.net/detail/infoworld/9904421