linux/unix多线程多进程编程总结(二)
linux/unix多线程,多进程编程是在实际工作中经常使用到的技能,在C语言或者C++语言面试的时候也经常会被问到此部分内容。
本文对linux/unix系统中的pthread相关的多进程和多线程编程的各个方面进行了总结,包括线程、进程、进程间通信及线程互斥等内容。一方面,给感兴趣的同事浏览斧正;另一方面,也是自己的一个技术笔记,方便以后回顾。
typedef unsigned long int pthread_t; //pthread_t是一个无符号长整型。
pthread_create (
pthread_t * tid, //线程id结构体
const pthread_attr_t * attr, //线程属性结构体
void * ( * start )(void *), //线程函数指针,返回是void *,参数也是void *
void * arg); //传递给线程函数的参数
//返回值:
// 成功:
// 返回0;
// 失败:
// 返回error number。
// EAGAIN: 达到了系统资源限制。
// EINVAL: attr设置无效。
// EPERM: 在attr属性中修改的设置没有权限。
代码一:
#include
#include
#include
void * test( void * par) {
int loop = 5,i = 0;
for(i = 0;i <5;i++) {
sleep(1);
printf("线程id是:%d\n", pthread_self()); //获得线程自身id。
}
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t; //线程结构体
pthread_create(&t, NULL, test, NULL); //创建线程
pthread_join(t,NULL); //等待线程执行结束(如果不加此句,那么新线程中test函数还没有执行结束主线程就退出了,
//而主线程退出会导致整个进程退出,因此如果没有此句,test()不能执行完成。)
return 0;
}
代码二:(向线程函数传递参数)
#include
#include
#include
typedef struct {
int a;
int b;
int c;
int d;
} A;
void * test( void * par) {
A * p = (A *)par; //类型转换
printf ("%d %d %d %d\n",p->a, p->b, p->c, p->d); //使用线程参数
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t;
A par;
par.a = 1; par.b = 2; par.c = 3; par.d = 4; //线程参数
pthread_create(&t, NULL, test, (void *)&par); //传递线程参数
pthread_join(t,NULL);
return 0;
}
int pthread_join( pthread_t thread, //线程id。
void ** retval ); //存储线程返回值的空间的地址。
//返回值:
// 成功返回0,
// 失败返回error number.
// EDEADLK: 检测到死锁(两个线程互相锁定或者thread参数是本线程。即线程自己不能join自己,否则返回EDEADLK错误。)
// EINVAL: thread参数无效或者另外一个线程正在等待join这个thread。
// ESRCH: 查不到线程Id。
#include
#include
#include
//对于同一个线程不能同时多次pthread_join,否则程序崩溃。
typedef struct {
int a;
int b;
} RET;
void * test(void * par) {
RET * p = malloc(sizeof(RET));
sleep(5);
p->a = 10;
p->b = 20;
pthread_exit((void *)p); //线程退出,并且返回退出状态。
}
void * test1(void * par) {
pthread_t t = *((pthread_t *)par);
void * pRet = NULL; //用于存储线程退出状态指针
printf("In test1, tid is %ld\n", t);
pthread_join(t, &pRet); //获得线程t的线程退出状态。
printf("Here.\n");
printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b); //打印线程退出返回的值。
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t, t1;
void * pRet;
pthread_create(&t, NULL, test, NULL); //创建运行test的线程。
printf("In main t tid is %ld\n", t);
pthread_create(&t1, NULL, test1, &t); //创建运行test1的线程。
pthread_join(t,&pRet); //此处join了t线程,在函数test1中也join了t线程。
pthread_join(t1, NULL);
printf("ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
return 0;
}
上面程序运行时发生崩溃,原因是对于线程t执行了两次pthread_join函数。
[root@localhost pthread]# ./a.out
In main t tid is 139668474418944
In test1, tid is 139668474418944
Here.
Segmentation fault (core dumped)
通过man pthread_join知道不能在多个线程中对同时对一个线程多次pthread_join.
man pthread_join结果如下:
DESCRIPTION
The pthread_join() function waits for the thread specified by thread to terminate. If that thread has already terminated, then pthread_join()
returns immediately. The thread specified by thread must be joinable.
(如果被join的线程已经结束,那么phread_join立即返回。)
If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that the target thread supplied to
pthread_exit(3)) into the location pointed to by *retval. If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.
If multiple threads simultaneously try to join with the same thread, the results are undefined. If the thread calling pthread_join() is canceled,
then the target thread will remain joinable (i.e., it will not be detached).
(如果多个线程尝试同时pthread_join同一个线程,那么结果是未定义的。但是如果调用pthread_join()的线程退出了,那么被join的线程仍然是joinable的,即可以被再次pthread_join。)
RETURN VALUE
On success, pthread_join() returns 0; on error, it returns an error number.
ERRORS
EDEADLK
A deadlock was detected (e.g., two threads tried to join with each other); or thread specifies the calling thread.
EINVAL thread is not a joinable thread.
EINVAL Another thread is already waiting to join with this thread.
ESRCH No thread with the ID thread could be found.
(此处说明了调用pthread_join最好检查返回值,至少能够在一定程度上避免死锁的发生。)
NOTES
After a successful call to pthread_join(), the caller is guaranteed that the target thread has terminated.
Joining with a thread that has previously been joined results in undefined behavior.
(join一个之前已经被joined线程会导致未定义的行为。)
Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread". Avoid doing this, since each zombie
thread consumes some system resources, and when enough zombie threads have accumulated, it will no longer be possible to create new threads (or
processes). (如果没有对线程调用pthread_join,那么会导致一个僵尸线程的出现,这样会占据系统资源,只有在进程退出的时候这个僵尸线程才被释放。)
There is no pthreads analog of waitpid(-1, &status, 0), that is, "join with any terminated thread". If you believe you need this functionality,
you probably need to rethink your application design.
All of the threads in a process are peers: any thread can join with any other thread in the process.
(所有线程,包括主线程都是对等的,也就是说:任何线程都可以join所在进程的其他线程。)
如何修改上面崩溃了的程序?(方法:对同一个线程只调用一次pthread_join,要么去掉test1中的pthread_join,要么去掉main函数中的pthread_join。)
正确的程序如下:
#include
#include
#include
//获得线程返回值
typedef struct {
int a;
int b;
} RET;
void * test(void * par) {
RET * p = malloc(sizeof(RET));
sleep(5);
p->a = 10;
p->b = 20;
pthread_exit((void *)p);
}
void * test1(void * par) {
//等待test()所在线程退出,并获取test()所在线程的返回值。
pthread_t t = *((pthread_t *)par);
void * pRet = NULL;
printf("In test1, tid is %ld\n", t);
pthread_join(t, &pRet); //对t只pthread_join了一次。
printf("Here.\n");
printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t, t1;
pthread_create(&t, NULL, test, NULL);
printf("In main t tid is %ld\n", t);
pthread_create(&t1, NULL, test1, &t);
pthread_join(t1, NULL);
return 0;
}
- 线程的joinable和detached状态。
#include
#include
#include
//获得线程返回值
typedef struct {
int a;
int b;
} RET;
void * test(void * par) {
RET * p = malloc(sizeof(RET));
sleep(5);
p->a = 10;
p->b = 20;
pthread_exit((void *)p);
}
void * test1(void * par) {
pthread_t t = *((pthread_t *)par);
void * pRet = NULL;
printf("In test1, tid is %ld\n", t);
pthread_join(t, &pRet);
printf("Here.\n");
printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t, t1;
pthread_create(&t, NULL, test, NULL);
pthread_create(&t1, NULL, test1, &t);
//pthread_exit(0);
return 0;
}
注意:要么在主线程中pthread_join,要么pthread_exit(0),否则当主线程执行完成,即使别的线程正在执行,那么整个进程也会结束。如上例,如果不加pthread_exit(0),则不会有任何打印;如果加上pthread_exit(0),则程序正确执行。
[root@localhost pthread]# ./a.out
In test1, tid is 140269881673472
Here.
Func test1. ret is 10, 20
获得当前线程的id号,见代码一。
函数原型 pthread_t pthread_self( void );
函数原型: int pthread_equal(pthread t1, pthread t2);
函数说明:
判断两个线程的id是否相等,即传递过来的两个线程是否是同一线程。
t1 == t2,返回非0;
t1 != t2,返回0。
验证pthread_equal:
#include
#include
#include
void * test(void * par) {
sleep(1);
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t;
pthread_create(&t, NULL, test, NULL);
//if(pthread_equal(t,t) == 0) {
if(pthread_equal(t, pthread_self()) == 0) {
printf("threads are different: %ld %ld\n", t, pthread_self());
} else {
printf("threads are same: %ld %ld\n", t, pthread_self());
}
return 0;
}
man手册说明:
Calling pthread_attr_init() on a thread attributes object that has already been initialized results in undefined behavior. (对于已经初始化了的pthread_attr_t不应该再次被调用pthread_attr_init,否则会导致未定义行为,即手册建议最好只调用一次。)
When a thread attributes object is no longer required, it should be destroyed using the pthread_attr_destroy() function. Destroying a thread attributes object has no effect on threads that were created using that object. (对pthread_attr_t结构体的销毁不会影响用这个属性已经创建完成的线程。)
Once a thread attributes object has been destroyed, it can be reinitialized using pthread_attr_init(). Any other use of a destroyed thread attributes object has undefined results. (pthread_attr_t被销毁之后可以再次调用pthread_attr_init进行初始化,不初始化就使用已经销毁了的attr会导致未定义行为。)
DESCRIPTION
The pthread_cancel() function sends a cancellation request to the thread thread. Whether and when the target thread reacts to the cancellation
request depends on two attributes that are under the control of that thread: its cancelability state and type.
A thread's cancelability state, determined by pthread_setcancelstate(3), can be enabled (the default for new threads) or disabled. If a thread has
disabled cancellation, then a cancellation request remains queued until the thread enables cancellation. If a thread has enabled cancellation,
then its cancelability type determines when cancellation occurs.
A thread's cancellation type, determined by pthread_setcanceltype(3), may be either asynchronous or deferred (the default for new threads). Asyn‐
chronous cancelability means that the thread can be canceled at any time (usually immediately, but the system does not guarantee this). Deferred
cancelability means that cancellation will be delayed until the thread next calls a function that is a cancellation point. A list of functions
that are or may be cancellation points is provided in pthreads(7).
When a cancellation requested is acted on, the following steps occur for thread (in this order):
1. Cancellation clean-up handlers are popped (in the reverse of the order in which they were pushed) and called. (See pthread_cleanup_push(3).)
2. Thread-specific data destructors are called, in an unspecified order. (See pthread_key_create(3).)
3. The thread is terminated. (See pthread_exit(3).)
The above steps happen asynchronously with respect to the pthread_cancel() call; the return status of pthread_cancel() merely informs the caller
whether the cancellation request was successfully queued.
After a canceled thread has terminated, a join with that thread using pthread_join(3) obtains PTHREAD_CANCELED as the thread's exit status. (Join‐
ing with a thread is the only way to know that cancellation has completed.)
#include
#include
#include
#include
int count = 0;
pthread_mutex_t mutex;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /静态初始化互斥量
void * test( void * par ) {
int i = 0;
for(i = 0;i < 10; i++) {
sleep(2);
pthread_mutex_lock(&mutex); //互斥量加锁
count += 1;
printf("%d\n", count);
pthread_mutex_unlock(&mutex); //互斥量解锁:
}
}
void * test1(void * par) {
int ret = 0, i = 0;
for(i = 0; i < 10;i++) {
ret = pthread_mutex_trylock(&mutex); //尝试加锁
if(ret == EBUSY) { //如果锁被占用,那么立即返回EBUSY
printf("Mutex EBUSY.\n");
} else if(ret == 0) { //表示加锁成功
printf("Try lock successfully.\n");
count++;
pthread_mutex_unlock(&mutex); //只有在加锁成功之后才能释放互斥锁
}
sleep(1);
}
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL); //互斥量初始化
pthread_create(&t1, NULL, test, NULL);
pthread_create(&t2, NULL, test, NULL);
pthread_create(&t4, NULL, test1, NULL);
pthread_create(&t3, NULL, test, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex); //互斥量销毁
}
#include
#include
#include
#include
typedef struct {
pthread_mutex_t _mutex;
pthread_cond_t _cond;
} A;
A a = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
void * producer(void * par) {
sleep(3);
for(int i = 0; i < 1; i++) {
sleep(1);
printf("Sending info.\n");
pthread_mutex_lock(&(a._mutex)); //先加锁再wait。
//pthread_cond_signal(&(a._cond)); //signal的话,只有一个wait的线程会执行。
pthread_cond_broadcast(&(a._cond)); //broadcast的话,两个wait的线程都会执行。
pthread_mutex_unlock(&(a._mutex));
}
printf("producer exit.");
return NULL;
}
void * consumer(void * par) {
pthread_mutex_lock(&(a._mutex)); //先加锁再wait.
pthread_cond_wait(&(a._cond), &a._mutex);
pthread_mutex_unlock(&(a._mutex));
printf("Consumer wake up. %ld\n", pthread_self());
return NULL;
}
int main(int argc, char ** argv) {
pthread_t t1,t2,t3;
pthread_create(&t1, NULL, consumer, NULL);
pthread_create(&t2, NULL, producer, NULL);
pthread_create(&t3, NULL, consumer, NULL);
pthread_exit(0);
}
#include
#include
#include
#include
#include
#include
using namespace std;
//信号量的值可以大于1,例如为10,sem_wait会把信号量减1,sem_post会把信号量加1.
//信号量的所有函数都是失败返回-1,对应错误代码存储在errno里。
sem_t sem;
void * fun1(void * par) {
while(1) {
sem_wait(&sem); //减1,如果到0则等待。
cout << "Wait tid: " << pthread_self() << endl;
}
return NULL;
}
void * fun2(void * par) {
while(1) {
sleep(2);
sem_post(&sem); //信号量加1.
sem_post(&sem); //信号量加1.
sem_post(&sem); //信号量加1.
cout << "Post tid: " << pthread_self() << endl;
}
return NULL;
}
int main(int argc, char ** argv) {
sem_init(&sem, 0, 5); //初始化信号量。
pthread_t t1,t2,t3;
pthread_create(&t1, NULL, fun1, NULL);
pthread_create(&t2, NULL, fun1, NULL);
pthread_create(&t3, NULL, fun2, NULL);
pthread_exit(0);
return 0;
}
sem_init() initializes the unnamed semaphore at the address pointed to by sem. The value argument specifies the initial value for the semaphore.
The pshared argument indicates whether this semaphore is to be shared between the threads of a process, or between processes. (pshared参数表示信号量是否可以在进程之间共享。)
If pshared has the value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global variable, or a variable allocated dynamically on the heap).
If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)). (Since a child created by fork(2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post(3), sem_wait(3), etc. (如果pshared设置为非0,那么信号量可以在进程之间共享,并且如果想要在进程之间共享该信号量的话应该把信号量放置在共享内存区。)
Initializing a semaphore that has already been initialized results in undefined behavior.
(一个信号量不能被初始化两次。)
#include
#include
#include
using namespace std;
pthread_rwlock_t rwlock;
void * reader(void * par) {
while(1) {
pthread_rwlock_rdlock(&rwlock);
sleep(1);
cout << "Pthread is locked: " << pthread_self() << endl;
pthread_rwlock_unlock(&rwlock);
}
return NULL;
}
void * writer(void * par) {
while(1) {
pthread_rwlock_wrlock(&rwlock);
cout << "Pthread is locked by writter." << endl;
pthread_rwlock_unlock(&rwlock);
sleep(2);
}
return NULL;
}
int main( int argc, char ** argv) {
pthread_rwlock_init(&rwlock, NULL);
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, reader, NULL);
pthread_create(&t2, NULL, writer, NULL);
pthread_create(&t3, NULL, reader, NULL);
pthread_exit(0);
return 0;
}