It could probably be a simple question but I couldn't find a clear answer for it. I have multiple threads in c code and one of them uses select to wait for n seconds. The question that I have is that does it blocks the entire process for n seconds (like usleep) or does select blocks only the calling thread (more like nanosleep).
Thanks for the answers.
I've seen several implementations in which one thread is blocking on select while other threads continue processing - so, yes, it only blocks the running thread.
(Sorry for not bringing any references)
The POSIX spec for select specifically mentions "thread" in only one place, where it talks about restoring the signal mask of the calling thread by pselect().
As with the other answers, my experience also says the answer is yes, it only blocks the calling thread.
Yes. A sloppy but still pretty conclusive test.
#include <iostream>
#include <pthread.h>
#include <sys/time.h>
using namespace std;
pthread_mutex_t cout_mutex = PTHREAD_MUTEX_INITIALIZER;
void *task1(void *X)
{
timeval t = {0, 100000};
for (int i = 0; i < 10; ++i)
{
pthread_mutex_lock(&cout_mutex);
cout << "Thread A going to sleep" << endl;
pthread_mutex_unlock(&cout_mutex);
select(0, NULL, NULL, NULL, &t);
pthread_mutex_lock(&cout_mutex);
cout << "Thread A awake" << endl;
pthread_mutex_unlock(&cout_mutex);
}
return (NULL);
}
void *task2(void *X)
{
pthread_mutex_lock(&cout_mutex);
cout << "Thread B down for the long sleep" << endl;
pthread_mutex_unlock(&cout_mutex);
timeval t = {5, 0};
select(0, NULL, NULL, NULL, &t);
pthread_mutex_lock(&cout_mutex);
cout << "Thread B glad to be awake" << endl;
pthread_mutex_unlock(&cout_mutex);
return (NULL);
}
int main(int argc, char *argv[])
{
pthread_t ThreadA,ThreadB;
pthread_create(&ThreadA,NULL,task1,NULL);
pthread_create(&ThreadB,NULL,task2,NULL);
pthread_join(ThreadA,NULL);
pthread_join(ThreadB,NULL);
return (0);
}
Related
I am working on a simulator for an embedded operating system that uses 5 threads. One acts as the scheduler thread and the other 4 are worker threads. The order of operation is always the same: the scheduler executes between every other thread and goes on activating the one that should go next, like this:
SchThread Thread1 SchThread Thread2 SchThread Thread3 SchThread Thread4 SchThread Thread1 ... and so on.
This is currently done in Windows with some bad practices and I'm tasked with switching it to a pthreads implementation. I know this is not how threads are supposed to work because the workload is fully sequential, but the simulator relies on having the 5 threads mentioned.
My question is: how am I supposed to implement this behaviour? Are mutexes or conditions enough? Should I use barriers? Maybe signals from the scheduler to wake up worker threads?
I've seen posts like this one (execution of pthreads in a particular order) that works with 2 threads, but I've failed in my attempts to increase the number to 5. Is using pthread_cond_ a good solution to this problem or am I focusing it wrong?
I've also tried doing the synchronization using mutexes or semaphores unsuccessfully. I've thought of a shared variable that manages the turn but I've been unable to make it work properly.
This is an opinion, because there's more than one way to solve your problem.
I would use a shared global variable, currentThread, whose value identifies which thread should run, and I would use a condition variable to let threads notify each other when currentThread changes.
Sorry I don't have time to write an example at this moment, but you can find examples in the man pages for pthread_cond_t, pthread_cond_wait(...), and pthread_cond_broadcast(...)
I've been thinking on asking this for a few days and the day I post the question I find a possible solution to this problem.
Continuing the explanation of the answer linked in the question, the solution looks like this:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> /* sleep */
#define MS100 100000
#define HALFS 500000
pthread_mutex_t m_sch;
pthread_cond_t SCH_GO;
pthread_cond_t THREAD2_GO;
pthread_cond_t THREAD3_GO;
unsigned turno = 1;
/* get next turn */
int sig_turno() {
static int anterior = 0;
int ret;
switch (anterior) {
case 2:
ret = 3;
break;
case 3:
ret = 2;
break;
default: /* first time */
ret = 2;
break;
}
anterior = ret;
return ret;
}
int sch_ret = 0;
void *sch(void *arg) {
int turno_local;
while (1) {
pthread_mutex_lock(&m_sch);
while (turno != 1) {
pthread_cond_wait(&SCH_GO, &m_sch);
}
printf("[THREAD SCH]\n");
usleep(MS100);
turno_local = sig_turno();
turno = turno_local;
switch (turno_local) {
case 2:
pthread_cond_signal(&THREAD2_GO);
break;
case 3:
pthread_cond_signal(&THREAD3_GO);
break;
default:
printf("error.\n");
break;
}
pthread_mutex_unlock(&m_sch);
}
sch_ret = 1;
return &sch_ret;
}
int thread2_ret = 0;
void *thread2(void *arg) {
while (1) {
pthread_mutex_lock(&m_sch);
while (turno != 2) {
pthread_cond_wait(&THREAD2_GO, &m_sch);
}
printf("[THREAD 2]\n");
usleep(HALFS);
turno = 1;
pthread_cond_signal(&SCH_GO);
pthread_mutex_unlock(&m_sch);
}
thread2_ret = 2;
return &thread2_ret;
}
int thread3_ret = 0;
void *thread3(void *arg) {
while (1) {
pthread_mutex_lock(&m_sch);
while (turno != 3) {
pthread_cond_wait(&THREAD3_GO, &m_sch);
}
printf("[THREAD 3]\n");
usleep(HALFS);
turno = 1;
pthread_cond_signal(&SCH_GO);
pthread_mutex_unlock(&m_sch);
}
thread3_ret = 3;
return &thread3_ret;
}
int main() {
void *ret;
pthread_t thread_sch, thread_2, thread_3;
pthread_cond_init(&SCH_GO, NULL);
pthread_cond_init(&THREAD2_GO, NULL);
pthread_cond_init(&THREAD3_GO, NULL);
pthread_mutex_init(&m_sch, NULL);
pthread_create(&thread_sch, NULL, sch, NULL);
usleep(MS100);
pthread_create(&thread_2, NULL, thread2, NULL);
pthread_create(&thread_3, NULL, thread3, NULL);
pthread_join(thread_sch, &ret);
pthread_join(thread_2, &ret);
pthread_join(thread_3, &ret);
printf("main() ending\n");
return 0;
}
Where sch is the scheduler's thread.
This can be extended to more threads and works as expected, even though it's just a draft. I've been unable to obtain similar behaviour using only mutexes, so conditions have been the way to go.
I do accept criticism and improvements to this solution, I don't really know if it is correct/good practice.
I'm playing around with io_uring, https://kernel.dk/io_uring.pdf, to see if it can be used for async file I/O for logging. This is a simple program that opens a file, stats the file, and then reads the first 4k from the file. This program runs to completion successfully when the file exists and is readable. But the user_data field in the completion queue entry is always zero. The documentation for io_uring says:
user_data is common across op-codes, and is untouched by the kernel. It's simply copied to the completion event, cqe, when a completion event is posted for this request.
Since the completions are not ordered the user_data field is needed to match completions with submissions. If the field is always zero then how can it be used?
#include <iostream>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <liburing.h>
#include <stdlib.h>
int main() {
struct io_uring ring;
// see man io_uring_setup for what this does
auto ret = io_uring_queue_init(64, &ring, 0);
if (ret) {
perror("Failed initialize uring.");
exit(1);
}
std::cout << "I/O uring initialized successfully. " << std::endl;
auto directory_fd = open("/tmp", O_RDONLY);
if (directory_fd < 0) {
perror("Failed to open current directory.");
exit(1);
}
struct io_uring_sqe *submission_queue_entry = io_uring_get_sqe(&ring);
submission_queue_entry->user_data = 100;
io_uring_prep_openat(submission_queue_entry, directory_fd, "stuff", O_RDONLY, 0);
submission_queue_entry = io_uring_get_sqe(&ring);
submission_queue_entry->user_data = 1000;
struct statx statx_info;
io_uring_prep_statx(submission_queue_entry, directory_fd, "stuff", 0, STATX_SIZE, &statx_info);
//TODO: what does this actually return?
auto submit_error = io_uring_submit(&ring);
if (submit_error != 2) {
std::cerr << strerror(submit_error) << std::endl;
exit(2);
}
int file_fd = -1;
uint32_t responses = 0;
while (responses != 2) {
struct io_uring_cqe *completion_queue_entry = 0;
auto wait_return = io_uring_wait_cqe(&ring, &completion_queue_entry);
if (wait_return) {
std::cerr << "Completion queue wait error. " << std::endl;
exit(2);
}
std::cout << "user data " << completion_queue_entry->user_data << " entry ptr " << completion_queue_entry << " ret " << completion_queue_entry->res << std::endl;
std::cout << "size " << statx_info.stx_size << std::endl;
io_uring_cqe_seen(&ring, completion_queue_entry);
if (completion_queue_entry->res > 0) {
file_fd = completion_queue_entry->res;
}
responses++;
}
submission_queue_entry = io_uring_get_sqe(&ring);
submission_queue_entry->user_data = 66666;
char buf[1024 * 4];
io_uring_prep_read(submission_queue_entry, file_fd, buf, 1024 * 4, 0);
io_uring_submit(&ring);
struct io_uring_cqe* read_entry = 0;
auto read_wait_rv = io_uring_wait_cqe(&ring, &read_entry);
if (read_wait_rv) {
std::cerr << "Error waiting for read to complete." << std::endl;
exit(2);
}
std::cout << "Read user data " << read_entry->user_data << " completed with " << read_entry->res << std::endl;
if (read_entry->res < 0) {
std::cout << "Read error " << strerror(-read_entry->res) << std::endl;
}
}
Output
I/O uring initialized successfully.
user data 0 entry ptr 0x7f4e3158c140 ret 5
size 1048576
user data 0 entry ptr 0x7f4e3158c150 ret 0
size 1048576
Read user data 0 completed with 4096
What happens if you try and set user_data after your calls to io_uring_prep_openat()/io_uring_prep_statx()?
I ask this because doing a Google search for io_uring_prep_statx suggests it comes from liburing library.
Searching the liburing source for io_uring_prep_openat leads us to a definition of io_uring_prep_openat() in liburing.h:
static inline void io_uring_prep_openat(struct io_uring_sqe *sqe, int dfd,
const char *path, int flags, mode_t mode)
{
io_uring_prep_rw(IORING_OP_OPENAT, sqe, dfd, path, mode, 0);
sqe->open_flags = flags;
}
Searching the liburing source for io_uring_prep_statx leads to a definition of io_uring_prep_statx():
static inline void io_uring_prep_statx(struct io_uring_sqe *sqe, int dfd,
const char *path, int flags, unsigned mask,
struct statx *statxbuf)
{
io_uring_prep_rw(IORING_OP_STATX, sqe, dfd, path, mask,
(__u64) (unsigned long) statxbuf);
sqe->statx_flags = flags;
}
Chasing the calls gets us to the definition of io_uring_prep_rw:
static inline void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,
const void *addr, unsigned len,
__u64 offset)
{
sqe->opcode = op;
sqe->flags = 0;
sqe->ioprio = 0;
sqe->fd = fd;
sqe->off = offset;
sqe->addr = (unsigned long) addr;
sqe->len = len;
sqe->rw_flags = 0;
sqe->user_data = 0;
sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
}
PS: I notice you have a comment that says
//TODO: what does this actually return?
auto submit_error = io_uring_submit(&ring);
Well, if we search the liburing repo for "int io_uring_submit" we come across the following in src/queue.c:
/*
* Submit sqes acquired from io_uring_get_sqe() to the kernel.
*
* Returns number of sqes submitted
*/
int io_uring_submit(struct io_uring *ring)
This ultimately chains calls down to io_uring_enter() syscall (raw man page) so you can read that for more detail.
Update: The questioner says moving the assignment solved their problem so I invested some time thinking about the text they quoted. Upon further reading I have picked up a subtlety (emphasis added):
user_data is common across op-codes, and is untouched by the kernel. It's simply copied to the completion event, cqe, when a completion event is posted for this request.
There's a similar statement earlier in the document (again emphasis added):
The cqe contains a user_data field. This field is carried from the
initial request submission, and can contain any information that the the application needs to identify said request. One common use case is to have it be the pointer of the original request. The kernel will not touch this field, it's simply carried straight from submission to completion event.
The statement applies to io_uring kernel syscalls but io_uring_prep_openat() / io_uring_prep_statx() are liburing functions. liburing is a userspace helper library so the statements above about user_data do not have to apply to all liburing functions.
If the field is always zero then how can it be used?
The field is being zeroed by certain liburing preparation helper functions. In this case it can be only be set (and retain the new value) after those helper function have been called. The io_uring kernel syscalls behave per the quote.
I'm trying to port a program from Windows to Linux.
I encountered a problem when I found out that there isn't a "real" ReadProcessMemory counterpart on Linux; I searched for an alternative and I found ptrace, a powerful process debugger.
I quickly coded two small console applications in C++ to test ptrace, before using it in the program.
TestApp
This is the tracee; it keeps printing two integers every 50 milliseconds while increasing their value by 1 every time.
#include <QCoreApplication>
#include <QThread>
#include <iostream>
using namespace std;
class Sleeper : public QThread
{
public:
static void usleep(unsigned long usecs){QThread::usleep(usecs);}
static void msleep(unsigned long msecs){QThread::msleep(msecs);}
static void sleep(unsigned long secs){QThread::sleep(secs);}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int value = 145;
int i = 0;
do {
cout << "i: " << i << " " << "Value: " << value << endl;
value++;
i++;
Sleeper::msleep(50);
} while (true);
return a.exec();
}
MemoryTest
This is the tracer; it asks for the process name and retrieves the PID using the command pidof -s, then ptrace attaches to the process and retrieves the memory address' value every 500 milliseconds, for 10 times.
#include <QCoreApplication>
#include <QThread>
#include <iostream>
#include <string>
#include <sys/ptrace.h>
#include <errno.h>
using namespace std;
class Sleeper : public QThread
{
public:
static void usleep(unsigned long usecs){QThread::usleep(usecs);}
static void msleep(unsigned long msecs){QThread::msleep(msecs);}
static void sleep(unsigned long secs){QThread::sleep(secs);}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
char process_name[50];
cout << "Process name: ";
cin >> process_name;
char command[sizeof(process_name) + sizeof("pidof -s ")];
snprintf(command, sizeof(command), "pidof -s %s", process_name);
FILE* shell = popen(command, "r");
char pidI[sizeof(shell)];
fgets(pidI, sizeof(pidI), shell);
pclose(shell);
pid_t pid = atoi(pidI);
cout << "The PID is " << pid << endl;
long status = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
cout << "Status: " << status << endl;
cout << "Error: " << errno << endl;
unsigned long addr = 0x012345; // Example address, not the true one
int i = 0;
do {
status = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
cout << "Status: " << status << endl;
cout << "Error: " << errno << endl;
i++;
Sleeper::msleep(500);
} while (i < 10);
status = ptrace(PTRACE_DETACH, pid, NULL, NULL);
cout << "Status: " << status << endl;
cout << "Error: " << errno << endl;
return a.exec();
}
Everything works fine, but TestApp is paused (SIGSTOP) until ptrace detaches from it.
Also, when it attaches to the process, the status is 0 and the error is 2; the first time it tries to retrieve the memory address value it fails with status -1 and error 3. Is it normal?
Is there a way to prevent ptrace from sending the SIGSTOP signal to the process?
I already tried using PTRACE_SEIZE instead of PTRACE_ATTACH, but it doesn't work: status -1 and error 3.
Update: Using Sleeper in MemoryTest before the "do-while" loop fixes the problem of the first memory address value retrieval, even if the value of seconds, milliseconds or microseconds is 0. Why?
After a lot of research I'm pretty sure that there isn't a way to use ptrace without stopping the process.
I found a real ReadProcessMemory counterpart, called process_vm_readv, which is much more simple.
I'm posting the code in the hope of helping someone who is in my (previous) situation.
Many thanks to mkrautz for his help coding MemoryTest with this beautiful function.
#include <QCoreApplication>
#include <QThread>
#include <sys/uio.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <iostream>
using namespace std;
class Sleeper : public QThread
{
public:
static void usleep(unsigned long usecs){QThread::usleep(usecs);}
static void msleep(unsigned long msecs){QThread::msleep(msecs);}
static void sleep(unsigned long secs){QThread::sleep(secs);}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
char process_name[50];
cout << "Process name: ";
cin >> process_name;
char command[sizeof(process_name) + sizeof("pidof -s ")];
snprintf(command, sizeof(command), "pidof -s %s", process_name);
FILE* shell = popen(command, "r");
char pidI[sizeof(shell)];
fgets(pidI, sizeof(pidI), shell);
pclose(shell);
pid_t pid = atoi(pidI);
cout << "The PID is " << pid << endl;
if (pid == 0)
return false;
struct iovec in;
in.iov_base = (void *) 0x012345; // Example address, not the true one
in.iov_len = 4;
uint32_t foo;
struct iovec out;
out.iov_base = &foo;
out.iov_len = sizeof(foo);
do {
ssize_t nread = process_vm_readv(pid, &out, 1, &in, 1, 0);
if (nread == -1) {
fprintf(stderr, "error: %s", strerror(errno));
} else if (nread != in.iov_len) {
fprintf(stderr, "error: short read of %li bytes", (ssize_t)nread);
}
cout << foo << endl;
Sleeper::msleep(500);
} while (true);
return a.exec();
}
Davide,
Have you had a look at the /proc filesystem? It contains memory map files that can be used to peek at the full process space. You can also write in the space to set a breakpoint. There is a wealth of other information in /proc as well.
The PTRACE_CONT command can be used to continue a process. Generally, the target will be paused with a PTRACE_ATTACH when the debugger attaches.
The man page says PTRACE_SIEZE should not pause the process. What flavor and version of Linux are you using? PTRACE_SIEZE has been around for quite awhile so I'm not sure why you are having trouble there.
I note the addr value is set to 0x12345. Is this a valid address in the target space? Or was that just an example? How is the stack address of interest (&value) communicated between the two processes?
I'm not too sure about the return codes. Generally a 0 means all is well, the errno may just be a hangover value from the last error.
--Matt
I'm trying to teach myself message queues, and I'm using pthreads that talk to each other.
I know that the buffer in mq_receive should be larger than attr.mq_msgsize, and it is (twice the size).
I haven't even sent a message yet.
EDIT: John Bollinger ran this on his machine and it worked. This may be an OS-dependent problem. I'm running Mint 18 XFCE
EDIT EDIT: rebooting fixed the behaviour? Not going to question this or complain.
This code is based off of an example I found online : https://github.com/arembedded/mq_example
Here is the code :
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
using namespace std;
#define PIN_MSG_NAME "/pin_msg"
#define DB_MSG_NAME "/db_msg"
#define MESSAGE_QUEUE_SIZE 15
pthread_t ATM;
pthread_t DB_server;
pthread_t DB_editor;
void* run_ATM(void* arg);
void* run_DB(void* arg);
static struct mq_attr mq_attribute;
static mqd_t PIN_MSG = -1;
void sig_handler(int signum){
//ASSERT(signum == SIGINT);
if (signum == SIGINT){
cout << "killing application" << endl;
pthread_cancel(ATM);
pthread_cancel(DB_server);
pthread_cancel(DB_editor);
}
}
int main(int argc, char const *argv[])
{
pthread_attr_t attr;
signal(SIGINT, sig_handler);
mq_attribute.mq_maxmsg = 10; //mazimum of 10 messages in the queue at the same time
mq_attribute.mq_msgsize = MESSAGE_QUEUE_SIZE;
PIN_MSG = mq_open(PIN_MSG_NAME , O_CREAT | O_RDWR, 0666, &mq_attribute);
if (PIN_MSG == -1){
perror("creating message queue failed ");
}
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 1024*1024);
long start_arg = 0; //the start argument is unused right now
pthread_create(&ATM, NULL, run_ATM, (void*) start_arg);
pthread_create(&DB_server, NULL, run_DB, (void*) start_arg);
pthread_join(ATM, NULL);
pthread_join(DB_server, NULL);
sig_handler(SIGINT);
}
void* run_ATM(void* arg) {
int status;
char accountNumber[15];
char PIN[15];
cout << "ATM is running" << endl;
cout << "Please input an account number > ";
cin >> accountNumber;
status = mq_send(PIN_MSG, accountNumber, sizeof(accountNumber) + 1, 1);
if (status < 0){
perror("sending message failed");
}
}
void* run_DB(void* arg){
cout << "Database server running" << endl;
int status;
char received_acct_number[30];
while(1){
status = mq_receive(PIN_MSG, received_acct_number, sizeof(received_acct_number), NULL);
if (status < 0){
perror("error:");
} else {
cout << received_acct_number << endl;
}
}
}
Am I missing something? Why is there a message coming in at all, and why is it too large?
You talk about your receive buffer size as if the error is reported by mq_receive(), but as you observe, your buffer is long enough to receive any message that can be enqueued on your queue, and moreover, you seem not to be expecting an incoming message at all.
Although I'm not sure how you could be confused in this way, I'm inclined to think that the problem occurs in sending a message:
char accountNumber[15];
...
status = mq_send(PIN_MSG, accountNumber, sizeof(accountNumber) + 1, 1);
Your queue's message length limit is 15 bytes, and you're trying to enqueue a 16-byte message. Moreover, your send buffer is in fact shorter than 16 bytes in the first place; if mq_send() tried to copy 16 bytes of message then that would produce undefined behavior.
I am just a beginner in Programming using C.For my college project I want to create a multi-threaded server application to which multiple clients can connect and transfer there data which can be saved in a database.
After going through many tutorials I got confused about how to create multiple threads using pthread_create.
Somewhere it was done like:
pthread_t thr;
pthread_create( &thr, NULL , connection_handler , (void*)&conn_desc);
and somewhere it was like
pthread_t thr[10];
pthread_create( thr[i++], NULL , connection_handler , (void*)&conn_desc);
I tried by implementing both in my application and seems to be working fine. Which approach of the above two is correct which I should follow.
sorry for bad english and description.
Both are equivalent. There's no "right" or "wrong" approach here.
Typically, you would see the latter when creating multiple threads, so an array of thread identifiers (pthread_t) are used.
In your code snippets, both create just a single thread. So if you want to create only one thread, you don't need an array. But this is just like declaring any variable(s) that you didn't use. It's harmless.
In fact, if you don't need the thread ID for any purpose, (for joining or changing attributes etc.), you can create multiple threads using a single thread_t variable without using an array.
The following
pthread_t thr;
size_t i;
for(i=0;i<10;i++) {
pthread_create( &thr, NULL , connection_handler , &conn_desc);
}
would work just fine. Note that the cast to void* is unnecessary (last argument to pthread_create()). Any data pointer can be implicitly converted to void *.
Sample Example of multiple thread :
#include<iostream>
#include<cstdlib>
#include<pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data
{
int thread_id;
char *message;
};
void *PrintHello(void *threadarg)
{
struct thread_data *my_data;
my_data = (struct thread_data *) threadarg;
cout << "Thread ID : " << my_data->thread_id ;
cout << " Message : " << my_data->message << endl;
pthread_exit(NULL);
}
int main ()
{
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc, i;
for( i=0; i < NUM_THREADS; i++ )
{
cout <<"main() : creating thread, " << i << endl;
td[i].thread_id = i;
td[i].message = "This is message";
rc = pthread_create(&threads[i], NULL,
PrintHello, (void *)&td[i]);
if (rc){
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
The first one you provided creates a single thread.
The second one (if looped) has the potential to spawn 10 threads.
Basically the pthread_t type is a handler for the thread so if you have an array of 10 of them then you can have 10 threads.
For everyone, use a thread list because it will not be free if you call once pthread_join().
Code exemple :
int run_threads(void)
{
int listLength = 5;
pthread_t thread[listLength];
//Create your threads(allocation).
for (int i = 0; i != listLength; i++) {
if (pthread_create(&thread[i], NULL, &routine_function, NULL) != 0) {
printf("ERROR : pthread create failed.\n");
return (0);
}
}
//Call pthread_join() for all threads (they will get free) and all threads
//will terminate at this position.
for (int i = 0; i != listLength; i++) {
if (pthread_join(thread[i], NULL) != 0) {
printf("ERROR : pthread join failed.\n");
return (0);
}
}
//return 1 when all threads are terminated.
return (1);
}