用C++11学习多线程

有了线程,多核才有了真正意义上的并发。在单核时代,所谓的并发只是伪并发,类似于事件轮询的方式。只是轮询的周期比较快,所以对于我们人类来说看上去和真的并发没什么区别。如果把主人公换成复仇者联盟2上的快银,在他的世界观里。。。呵呵
并发毫无疑问能提高工作效率,但也肯定会有一些问题需要注意。很明显的一个问题就是,对于共享资源的访问。因为线程之间是栈私有,堆共有的。对于局部变量不会有什么问题,但如果两个线程同时访问一个共享变量,这个时候就需要注意了。为了处理并发带来的隐患,我们发明了互斥量,信号量,条件变量,屏障等概念来处理线程对共享资源的访问。
在只有进程的年代,我们貌似只能通过fork之类的方式来复制一个进程,进程之间貌似没有同步的概念。进程之间只有通信。对于Unix IPC和网络编程,我肯定推荐Richard的著作,APUE和Unix网络编程。他老人家已经于1999年去世了,希望在天堂一切都好吧。
对于并发,不同平台有不同的实现。Windows上有Win32的API供你调用,Linux上也有一套自己的接口。好在POSIX的出现为我们编写可移植的程序提供了方便。同时C++11中以提供了一套跨平台的线程库。具体看见:传送门
用C++11写一个多线程的小程序:

#include <iostream>
#include <thread>
#include <cassert>
using namespace std;
//calc the Nth items of Fib series
void f1(int n){
    assert(n>0);
    for(int i=0;i<5;i++){
        cout<<"thread f1 is running."<<endl;
        ++n;
    }
}
void f2(int &n){    
    assert(n>=0);
    for(int i=0;i<5;i++){
        cout<<"thread f2 is running."<<endl;
        ++n;
    }
}
int main(void){
    int n=0;
    std::thread t1(f1,n+1);
    std::thread t2(f2,std::ref(n));
    t1.join();
    t2.join();
    cout<<"At the last the n's value is: "<<n<<endl;
    return 0;
}

程序就是上面这样,很简单。创建两个线程,每个线程各有一条打印语句。其中第二个线程是传引用。多运行几次程序,你就会发现两个线程的打印语句的执行顺序是不确定的,原因完全是因为处理器的调度。
代码也没什么好说的,只是要注意

thread(const thread&) = delete;

这是一个被删除的函数,也就是说不支持拷贝赋值。

例子2 线程报数
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <atomic>
using namespace std;
atomic<int> login_count;
int thread_num=50;   //创建线程数
int TIMES=20;        //查看次数
void f(void){
    login_count.fetch_add(1,std::memory_order_relaxed);
    Sleep(50);
}
int main(int argc,char **argv){
    thread pool[thread_num];
    while(TIMES-->0){
        login_count=0;
        for(int i=0;i<thread_num;i++){
            thread t(f);
            pool[i]=std::move(t);   
        }
        //wait every thread finish his task
        for(int i=0;i<thread_num;i++)
            pool[i].join();

        cout<<"50 users login.The output is: "<<login_count<<endl;
    }
    return 0;   
}

程序的功能如下:启动一个线程,就报一个数。可见于某网站统计用多少人登录吧。这里主要用到了原子操作。

login_count.fetch_add(1,std::memory_order_relaxed);

打印结果见下图。是没有错误的,不过看官可以试一下不用原子操作。也就是最野性的

longin_count++:

结果肯定不正确。理由也很简单:自加操作的汇编代码有三步:

1:move data from memory to register
2:add the data in register
3:move data back to memory

所以如果第一个线程执行到在寄存器中加1时被CPU挂起,此时运行第二个线程。。。。。

除了原子操作,互斥量和信号量都能用来应对这种情况。所谓至于为什么叫原子操作,类比我们物理中的原子啦。是不可分割的最小单位,编程中的原子性是指。一个操作要么做完,要么不做。比如你像朋友打钱,打到一半断电了。。。这时候怎么办?
数据库中也有类似的概念,貌似叫做事务性。

java version
import java.util.concurrent.*;

public class my_runnable implements Runnable{
    int count=1,number;
    //constructor
    public my_runnable(int number){
        this.number=number;
    }
    public void run(){
        while(true){
            System.out.println("thread ID is: " + number+"  current number is: " + count);
            if(++count==6)
                return ;
        }
    }
    public static void main(String[] args){
        for(int i=0;i<2;i++)
            new Thread(new my_runnable(i+1)).start();
    }
}

运行一下很明显就能看到多线程的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值