有了线程,多核才有了真正意义上的并发。在单核时代,所谓的并发只是伪并发,类似于事件轮询的方式。只是轮询的周期比较快,所以对于我们人类来说看上去和真的并发没什么区别。如果把主人公换成复仇者联盟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();
}
}
运行一下很明显就能看到多线程的效果。