為什么選擇多線程?而不是多進程?
比起多進程來說,線程間通信簡單(全局變量就可以了),而多進程之間的通信相對而言更繁瑣一些,呵呵...
我們的問題如何產生的?問題的根本原因是什么?
事情是這樣的,模塊之間需要通信,我們用了openwrt的開源代碼ubus做消息轉發
在我們的每個需要通信的模塊中創建了一個線程(ubus thread)循環接收ubusd轉發而來的消息(用的是libubox提供的API uloop_run)
在模塊需要發送時,主線程調用ubus的消息發送接口(ubus_send_event)
大多數情況是這樣的:
1. 模塊(作為一個后台進程)啟動時裝載了libubox.so(注意,這個庫的載入時加入了一個全局變量 int epoll_fd)
2. 隨即就創建線程ubus thread循環等待接收消息,ubus thread的動作大致如下:
a. 建立和ubusd通信的socket(記為fd1)
b. 隨即調用epoll_ctl將fd1加入到epoll_fd中
c. 然后調用epoll_wait....
3. 主線程執行,在有需要的時候,調用ubus_send_event發送
實際上這個庫接口做的動作是這樣的:
a. 建立和ubusd通信的socket(記為fd2)
b. 將fd2加入到全局變量epoll_fd,然后執行epoll_wait(epoll_fd沒有創建的話,先調用epoll_create)
那么問題來了,一般都是ubus thread先執行,然后阻塞在epoll_wait中,主線程在ubus thread阻塞時將fd2加入到同一個epoll_fd中....
也就是兩線程在操作同一個epoll_fd
結果fd2狀態發生變化時,ubus thread被喚醒執行了,然后就亂套了......
嘗試了一下,在主線程發送的時候去fork子進程,子進程完成發送的動作,反而更悲劇了,整個進程崩掉了....
為啥呢,因為fork出來的子進程沒有執行exec切換進程執行上下文,完全是一個父進程的拷貝,那個出問題的epoll_fd也被它拿着了
然后子進程新建了一個和ubusd通信的fd2,加入到這個epoll_fd中,然后執行epoll_wait
結果fd2發生狀態變化時,內核喚醒了ubus thread,尼瑪,ubus thread哪里知道fd2的存在,結果非法地址訪問,Segmentfault....呵呵...
最后,最后,因為我們這群小蜜蜂實在是飛得太低,所以,我們借助了ubus源碼附帶提供的ubus命令行工具
在主線程發送時 system("ubus send %s ", message)
這就相當於在主線程發送時fork子進程,然后子進程的執行環境切換到ubus工具,ubus工具會調用ubus_send_event
另一種解決方案:
還是在主線程發送的時候去fork子進程,子進程一上來直接去循環關閉從0-1024的fd,哈哈哈哈...(不知道會不會有什么其他的影響,不過我覺得應該可以)
for (i = 0; i < FDSET; i++)
close(i)
這樣做是強迫子進程再次去調用epoll_create,而不是復用從父進程那里繼承而來的epoll_fd。
所以,用了開源庫,加上我們獨特的線程設計,給自己整了個大坑....