c++ 多线程

    关于c++多线程的例子网上很多, 教程也很多, 我这里就对于入门之类的就不做过多的探讨, 首先来看下几个问题

  • 一个程序里有多少个栈?
这个问题一看确实有点懵, 不急, 其实他要问的就是多线程的问题。 对于一个多线程的程序, 每个线程内的栈式独立的, 所以 有多少个线程就有多少个栈。 

从发展的历史来看, 刚开始的单进程, 后来分布式发展多进程程序也发展而来, 后来的多核发展, 多进程不合适了, 原因是由于, 进程和进程间数据分割, 进程的切换工作量太大, cpu浪费大量的时间去保存和恢复进程工作的现场。 所以多线程程序发展而来。线程切换代价小, 共享数据方便,所以非常适合在多核的机器上运行。那么来问第二个问题。
  • 多线程好还是多进程好
这个问题很好回答, 各有各的好, 说到这也许有人会说, 这不相当于没说? 的确是! 多线程虽然有很多好处但是不代表他没坏处啊, 由于共享数据, 导致他数据同步非常复杂, 线程切换是方便了, 单机的性能是好了, 但是如果放眼整个集群上千台机器呢? 这个时候考虑的重点往往是可靠性, 多线程程序如果一个线程崩溃, 往往会导致程序整个崩溃, 但是多进程就不同, 互相独立的, 就像P2P可靠性增强了。 所以这个还真是要看使用的情况, 要辩证的看

好了, 扯了这么多闲篇我们来看具体的:

第一个例子很简单, 创建多个线程, 每个线程不断的实例化一个对象
void *t_func(void *arg) {
        for (i=0; i<100000000; i++) {
                Object obj;
        }

}
int main() {
        int threadcnt = 80;
        pthread_t  *threads = new pthread_t[threadcnt];
        for (int i=0; i<threadcnt; i++) {
                pthread_t thread;
                if (pthread_create( &thread, NULL, t_func, NULL) != 0 ) {
                        cout << "create thread faild" << endl;
                        return -1;
                }
                else
                threads[i] = thread;
        }
        for (int i=0; i<threadcnt; i++) {
                pthread_join(threads[i], NULL);
        }
        return 0;
}
这个程序就是个多线程的程序, 虽然没什么意义, 但是确是让我们学会了创建线程等工作
  • pthread_create
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数的解释, thread是一个线程变量的指针, 他要记录线程创建后的ID,这个是内核调度时唯一的一个标识, attr是线程的属性, 我们用的是null, 所以相当于用默认属性,当然你也可以自己去定义,主要是规定线程的栈的大小,一些属性, 第三个参数就是线程要执行的代码, arg是我们要传递的参数, 如果创建成功, 返回0.
  • pthread_join
#include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);

这个函数是要在主线程调用, 用于管理线程的退出状态, 防止资源的泄露,  返回0 表示成功, retval指向的是线程返回时的内存地址。  这个函数的关键作用是阻塞主线程,等待子线程执行完毕,再退出。 至于为什么不调用该函数会造成内存泄露是因为系统为每个线程分配的私有资源会一直保留,直到线程终止。 终止线程只能调用这个函数。 当然对于分离线程例外, 分离线程指的是创建的线程跟主线程没关系, 这个在创建线程的 attr里可以指定。
关于主线程和线程退出的例子可以参看 http://blog.csdn.net/l09711/article/details/9865489 , 其实我们还可以学习多进程里的父进程和子进程的管理, 对比下以后不容易混淆。 跟多线程一样, 当父进程先退出, 子进程就变成了孤儿进程, 会有init 进程来管理,如果子进程退出, 父进程没有 waitpid()之类的操作, 那子进程虽然结束, 但是没有被销毁,这种状态叫僵死进程, 其实一般不用担心, 但是假设父进程不退出, 那就比较麻烦, 因为会有很多的僵死进程。

上面我们提到线程间资源是共享的, 所以信息同步比较复杂, 下面我们就看一下信息同步方法: 
  • 互斥锁 mutex

同一个锁只能lock 一次, 当有对已加锁的mutex对象再加锁, 就会被阻塞住, 直到该mutex的上一个加锁者释放, 一个简单的例子

pthread_mutex_t lock1, lock2;
void * thread_1(void *arg) {
        pthread_mutex_lock( &lock1 );
        printf("thread 1 got the lock\n");
        sleep(3);
        pthread_mutex_unlock(&lock1);
}

void * thread_2(void *arg) {
        pthread_mutex_lock( &lock1 );
        printf("thread 2 got the lock\n");
        sleep(3);
        pthread_mutex_unlock(&lock1);
}


int main () {
        pthread_t *t1, *t2;
        t1 = (pthread_t *) malloc(sizeof(pthread_t));
        if (t1 == NULL) {
                printf("Failed to malloc memory for thread 1\n");
                exit(1);
        }
        if (pthread_create(t1, NULL, thread_1, NULL) !=0 ) {
                printf("Failed to create thread 1\n");
                exit(1);
        }

        t2 = (pthread_t *) malloc(sizeof(pthread_t));
        if (t1 == NULL) {
                printf("Failed to malloc memory for thread 2\n");
                exit(1);
        }
        if (pthread_create(t2, NULL, thread_2, NULL) !=0 ) {
                printf("Failed to create thread 2\n");
                exit(1);
        }

        pthread_join(*t1, NULL);
        pthread_join(*t2, NULL);
        return 0;
}


 
修改下thread_1函数, 把他变成死锁的: 

void * thread_1(void *arg) {
        pthread_mutex_lock( &lock1 );
        printf("thread 1 got the lock\n");
        while(1);
        pthread_mutex_unlock(&lock1);
}

简单的程序这个问题很好解决, 在大型的程序里,只有极其复杂的情况下才会死锁, 怎么定位到哪里有死锁呢? 下面我们来看一种定位死锁的方式, 还是上面的代码, 运行后

jimmy@jimmy-desktop:~/EMP$ ./mutex 
thread 1 got the lock
这个时候top 或ps 看下进程id

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                
18471 jimmy     20   0 22852  380  296 S  100  0.0   1:10.62 mutex                                                                  
 2265 jimmy     20   0 1733m 156m  23m S    0  2.0 580:07.34 compiz

我们kill -11 这个时候会产生core文件:

jimmy@jimmy-desktop:~/EMP$ ./mutex 
thread 1 got the lock
[1]+  Segmentation fault      (core dumped) ./mutex
Segmentation fault (core dumped)

用 gdb打开, gdb mutex -c corefile,   这个时候不要去关注崩溃的地方, 我们实际上要看崩溃的时候的函数调用栈, 因为肯定有锁的地方,  查看命令式 thread apply all bt:

(gdb) thread apply all bt

Thread 3 (Thread 0x7f23f594e700 (LWP 18455)):
#0  __lll_lock_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:132
#1  0x00007f23f651a065 in _L_lock_858 () from /lib/x86_64-linux-gnu/libpthread.so.0
#2  0x00007f23f6519eba in __pthread_mutex_lock (mutex=0x601080) at pthread_mutex_lock.c:61
#3  0x00000000004007d4 in thread_2(void*) ()
#4  0x00007f23f6517e9a in start_thread (arg=0x7f23f594e700) at pthread_create.c:308
#5  0x00007f23f62443fd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#6  0x0000000000000000 in ?? ()

Thread 2 (Thread 0x7f23f614f700 (LWP 18454)):
#0  0x00000000004007bc in thread_1(void*) ()
#1  0x00007f23f6517e9a in start_thread (arg=0x7f23f614f700) at pthread_create.c:308
#2  0x00007f23f62443fd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#3  0x0000000000000000 in ?? ()

Thread 1 (Thread 0x7f23f692f700 (LWP 18453)):
#0  0x00007f23f6519148 in pthread_join (threadid=139792429152000, thread_return=0x0) at pthread_join.c:89
#1  0x00000000004008d2 in main ()
设个时候能看到 锁的位置方便我们排查问题。

  • 读写锁  rwlock
读写锁用法跟mutex类似, 但是要谨记 他适合的是读多写少的情况, 读写锁比mutex多一种状态, 总的来看就是 没有锁, 有读锁, 有写锁。 读锁之上可以再加读锁, 写锁不行,会block住其他的加锁操作。 看一段代码, 跟上面的类似:
pthread_rwlock_t lock1 ;
void * thread_1(void *arg) {
        pthread_rwlock_rdlock( &lock1 );
        printf("thread 1 got the read lock\n");
        sleep(3);
        pthread_rwlock_unlock(&lock1);
}

void * thread_2(void *arg) {
        
	pthread_rwlock_rdlock( &lock1 );
//pthread_rwlock_rwlock( &lock1 );  printf("thread 2 got the write lock\n");

        sleep(3);
        pthread_rwlock_unlock(&lock1);
}


main 函数同上

把注释掉的部分换换, 看看效果吧, 一个读锁上加读锁, 一个是读锁上加写锁。


  • 条件变量
       这个说起来有些复杂, 而且必须要跟 mutex一起使用,还是上个例子吧, 能清楚的看到他跟mutex的不同, 代码片段如下:
pthread_mutex_t lock1;
pthread_cond_t cond_lock = PTHREAD_COND_INITIALIZER;
int trigger = 0;

void * thread_1(void *arg) {
        pthread_mutex_lock( &lock1 );
        printf("thread 1 got the lock\n");
        while (trigger == 0) {
                printf("I am in loop\n");
                pthread_cond_wait(&cond_lock, &lock1);
                printf("I am in loop after wait\n");
        }
        printf("thread1 got trigger %d\n", trigger);
        pthread_mutex_unlock(&lock1);
}

void * thread_2(void *arg) {
        pthread_mutex_lock( &lock1 );
        printf("thread 2 got the lock\n");
        sleep(3);
        trigger++;
        pthread_mutex_unlock(&lock1);
        pthread_cond_signal(&cond_lock);
}

线程1先运行, 先对lock1加锁, 在while循环里, 当遇到 
pthread_cond_wait(&cond_lock, &lock1)
这时相当于对lock1解锁, 但实际没有,线程1进入条件等待,直到被触发, 所以 thread2能够顺利的加上对lock的锁。 线程2最后触发等待条件变量, 线程1继续执行, 最后真正对lock1解锁


  • 信号量
信号量用的比较多,跟进程的比, 他是轻量级, 用法很简单, 一个信号量可以赋予一个值, 然后不同线程对他调用 sem_post (信号量加1) sem_wait(信号量减一), 操作, 只有信号量大于0才会继续操作, post不会阻塞


具体例子 参见:http://blog.csdn.net/wtz1985/article/details/3835781  当然还有很多例子, 这个比较简单不详细描述。




























  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值