问题
I have a program that is representing a car making factory. A queue holds the car parts to be made, each number represents a different part of the car, ex. 4 is window, we need 7 windows:
queue(^: front, *: rear): [0^, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 7*]
num_workers represent the number of workers who can work parallely(threads). This is the part of the main where I call the thread function.
int num_work = num_cars * NUM_PARTS;
int k=0;
while(k<num_work){
for(int i=0;i<num_workers; i++){
pthread_mutex_lock(&lock);
if(k>=num_work)
break;
++k;
pthread_mutex_unlock(&lock);
if((rc = pthread_create(&thr[i], NULL ,workerThreadFuc, &wpack[i]))){
fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
return EXIT_FAILURE;
}
}
This is the thread function where each thread(worker) dequeues a job and makes it.
void* workerThreadFuc(void *arg){
// Extract work_pack
work_pack *wpack = (work_pack *)arg;
int tid = wpack->tid;
queue *jobQ = wpack->jobQ;
int jobID;
//TODO finish workerThreadFuc
queueDequeueFront(jobQ, &jobID);
work(jobID, wpack->resource, tid);
pthread_exit(NULL);
}
And this is an example from the work function on how the car part is made:
switch(jobID) {
case SKELETON :
makeSkeleton(pack->sem_space, pack->sem_skeleton);
#if DEBUG
printf("Worker[%d]: One car skeleton made!\n", tid);
fflush(stdout);
#endif
break;
case ENGINE :
makeEngine(pack->sem_space, pack->sem_engine);
#if DEBUG
printf("Worker[%d]: One engine made!\n", tid);
fflush(stdout);
#endif
break;
case CHASSIS :
and so on. The problem I have is, no matter how many cars I have, the time to dequeue and get all the jobs take the same time for one worker. For example, 1 worker takes 15 secs to complete 1 car but also takes the same amount of time for 5 or 10 cars.
And similarly, no matter how many workers I have, completion of one car and five cars are always the same.
This is the function called by the work function:
void makeItem(sem_t *space, int makeTime, sem_t* item) {
requestSpace(space);
printf("About to sleep for %d\n",makeTime );
sleep(makeTime);
printf("Woke up after %d\n",makeTime );
sem_post(item);
}
Output:
Job defined, 1 workers will build 1 cars with 100 storage spaces
About to sleep for 5
About to sleep for 4
About to sleep for 3
About to sleep for 1
About to sleep for 1
About to sleep for 1
About to sleep for 1
About to sleep for 1
About to sleep for 1
About to sleep for 1
About to sleep for 2
About to sleep for 2
About to sleep for 2
About to sleep for 2
About to sleep for 3
Woke up after 1
Woke up after 1
Woke up after 1
Woke up after 1
Woke up after 1
Woke up after 1
Woke up after 1
Woke up after 2
Woke up after 2
Woke up after 2
Woke up after 2
Woke up after 3
Woke up after 3
WILL BE REPORTING RESULTS
=====Production report=====
Number of workers: 1, Storage space size: 100
Unused Skeleton: 0
Unused Engine: 0
Unused Chassis: 1
Unused Body: 0
Unused Window: 0
Unused Tire: 0
Unused Battery: 0
There are waste car parts!
Production of 0 car done, production time: 3.000766 sec
The program is supposed to dequeue, sleep and wait until the time is over and dequeue a new job and sleep. But for some reason everything is dequeued immediately and seems like the program doesn't even sleep for each task.
回答1:
You attempted to create a new thread for each job. But you forgot to limit the number of concurrent workers as desired. If the number of exiting worker threads has reached the cap, you need to wait for one to complete before creating another.
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_WORKERS 4
#define NUM_JOBS 40
typedef struct {
int created;
int joinable;
pthread_t thread;
size_t job;
} Worker;
static int msleep(long msec) {
struct timespec ts;
int res;
if (msec < 0) {
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
// Shared variables.
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static Worker workers[MAX_WORKERS];
Worker *get_free_worker(void) {
pthread_mutex_lock(&mutex);
while (1) {
for (size_t w=MAX_WORKERS; w--; ) {
Worker *worker = workers + w;
if (worker->joinable) {
pthread_join(worker->thread, NULL);
worker->created = 0;
worker->joinable = 0;
}
if (!worker->created) {
worker->created = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return worker;
}
}
pthread_cond_wait(&cond, &mutex);
}
}
static void *worker_func(void *worker_) {
Worker *worker = (Worker*)worker_;
unsigned int seed = (uintptr_t)worker; // Whatever.
printf("Processing job %zu\n", worker->job);
// msleep( rand_r(&seed) % 1000 + 1000 ); // Simulate a 1 to 2s load.
printf(" Finished processing %zu\n", worker->job);
{
pthread_mutex_lock(&mutex);
worker->joinable = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(void) {
// For all w, workers[w].created and workers[w].joinable are
// already initialized to 0 by virtue of being in static storage.
for (size_t j=0; j<NUM_JOBS; ++j) {
Worker *worker = get_free_worker();
printf("Creating worker for job %zu\n", j);
worker->job = j;
if (errno = pthread_create(&(worker->thread), NULL, worker_func, worker)) {
perror("pthread_create");
worker->created = 0;
break;
}
}
for (size_t w=MAX_WORKERS; w--; ) {
Worker *worker = workers + w;
if (worker->created) {
pthread_join(worker->thread, NULL);
}
}
return 0;
}
A different approach is to create a static pool of worker threads being fed by queue. In this approach, the workers are reused for multiple jobs.
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM_WORKERS 4
#define QUEUE_SIZE 10
#define NUM_JOBS 40
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int done;
int empty;
int full;
size_t max;
size_t next_insert;
size_t next_read;
uintptr_t *buf;
} Queue;
static void Queue_init(Queue* q, size_t max) {
pthread_mutex_init(&(q->mutex), NULL);
pthread_cond_init(&(q->cond), NULL);
q->done = 0;
q->empty = 1;
q->full = 0;
q->max = max;
q->next_insert = 0;
q->next_read = 0;
q->buf = malloc(sizeof(unsigned)*max);
}
static void Queue_destroy(Queue *q) {
free(q->buf);
pthread_cond_destroy(&(q->cond));
pthread_mutex_destroy(&(q->mutex));
}
static void Queue_done(Queue *q) {
pthread_mutex_lock(&(q->mutex));
q->done = 1;
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}
// Returns the oldest item from the queue (via a parameter) and returns 1.
// If the queue is empty and done, returns 0.
// If the queue is empty and not done, waits until that changes.
static int Queue_dequeue(Queue *q, uintptr_t *i) {
pthread_mutex_lock(&(q->mutex));
while (q->empty && !q->done)
pthread_cond_wait(&(q->cond), &(q->mutex));
int dequeued;
if (q->empty) {
// We are completely done.
dequeued = 0;
} else {
*i = q->buf[ q->next_read ];
q->next_read = ( q->next_read + 1 ) % q->max;
q->empty = q->next_read == q->next_insert;
q->full = 0;
dequeued = 1;
}
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
return dequeued;
}
// Adds the argument to the queue.
// If the queue is full, waits until that changes.
static void Queue_enqueue(Queue *q, uintptr_t i) {
pthread_mutex_lock(&(q->mutex));
while (q->full && !q->done)
pthread_cond_wait(&(q->cond), &(q->mutex));
if (q->done) {
fprintf(stderr, "Error: Attempted to add item to \"done\" queue.\n");
} else {
q->buf[q->next_insert] = i;
q->next_insert = ( q->next_insert + 1 ) % q->max;
q->empty = 0;
q->full = q->next_insert == q->next_read;
}
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}
static int msleep(long msec) {
struct timespec ts;
int res;
if (msec < 0) {
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
// Shared variables.
static Queue q;
static void *worker_func(void *worker_id_) {
uintptr_t worker_id = (uintptr_t)worker_id_;
unsigned int seed = worker_id; // Whatever.
uintptr_t j;
while (Queue_dequeue(&q, &j)) {
printf("[%" PRIuPTR "] Dequeued %" PRIuPTR "\n", worker_id, j);
// msleep( rand_r(&seed) % 1000 + 1000 ); // Simulate a 1 to 2s load.
printf("[%" PRIuPTR "] Finished processing %" PRIuPTR "\n", worker_id, j);
}
return NULL;
}
int main(void) {
Queue_init(&q, QUEUE_SIZE);
pthread_t workers[NUM_WORKERS];
for (uintptr_t w=0; w<NUM_WORKERS; ++w) {
if (errno = pthread_create(&(workers[w]), NULL, worker_func, (void*)w)) {
perror(NULL);
exit(1);
}
}
for (uintptr_t j=0; j<NUM_JOBS; ++j) {
printf("[x] Enqueuing %" PRIuPTR "...\n", j);
Queue_enqueue(&q, j);
printf("[x] Enqueued %" PRIuPTR ".\n", j);
}
Queue_done(&q);
printf("[x] Called done.\n");
for (uintptr_t w=0; w<NUM_WORKERS; ++w)
pthread_join(workers[w], NULL);
Queue_destroy(&q);
return 0;
}
来源:https://stackoverflow.com/questions/59188137/how-does-a-single-thread-does-the-work-of-multiple-threads